diff options
author | orivej <orivej@yandex-team.ru> | 2022-02-10 16:45:01 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:45:01 +0300 |
commit | 2d37894b1b037cf24231090eda8589bbb44fb6fc (patch) | |
tree | be835aa92c6248212e705f25388ebafcf84bc7a1 /contrib/libs/llvm12/tools/llvm-rc | |
parent | 718c552901d703c502ccbefdfc3c9028d608b947 (diff) | |
download | ydb-2d37894b1b037cf24231090eda8589bbb44fb6fc.tar.gz |
Restoring authorship annotation for <orivej@yandex-team.ru>. Commit 2 of 2.
Diffstat (limited to 'contrib/libs/llvm12/tools/llvm-rc')
15 files changed, 5011 insertions, 5011 deletions
diff --git a/contrib/libs/llvm12/tools/llvm-rc/Opts.td b/contrib/libs/llvm12/tools/llvm-rc/Opts.td index 27b4f182a2..613f0a0db3 100644 --- a/contrib/libs/llvm12/tools/llvm-rc/Opts.td +++ b/contrib/libs/llvm12/tools/llvm-rc/Opts.td @@ -1,55 +1,55 @@ -include "llvm/Option/OptParser.td" - -// All the switches can be preceded by either '/' or '-'. -// These options seem to be important for the tool -// and should be implemented. - +include "llvm/Option/OptParser.td" + +// All the switches can be preceded by either '/' or '-'. +// These options seem to be important for the tool +// and should be implemented. + def fileout : JoinedOrSeparate<[ "/", "-" ], "FO">, - HelpText<"Change the output file location.">; - + HelpText<"Change the output file location.">; + def define : Separate<[ "/", "-" ], "D">, - HelpText<"Define a symbol for the C preprocessor.">; + HelpText<"Define a symbol for the C preprocessor.">; def undef : Separate<[ "/", "-" ], "U">, - HelpText<"Undefine a symbol for the C preprocessor.">; - + HelpText<"Undefine a symbol for the C preprocessor.">; + def lang_id : JoinedOrSeparate<[ "/", "-" ], "L">, - HelpText<"Set the default language identifier.">; + HelpText<"Set the default language identifier.">; def lang_name : Separate<[ "/", "-" ], "LN">, - HelpText<"Set the default language name.">; - + HelpText<"Set the default language name.">; + def includepath : Separate<[ "/", "-" ], "I">, HelpText<"Add an include path.">; def noinclude : Flag<[ "/", "-" ], "X">, HelpText<"Ignore 'include' variable.">; - + def add_null : Flag<[ "/", "-" ], "N">, - HelpText<"Null-terminate all strings in the string table.">; - + HelpText<"Null-terminate all strings in the string table.">; + def dupid_nowarn : Flag<[ "/", "-" ], "Y">, - HelpText<"Suppress warnings on duplicate resource IDs.">; - + HelpText<"Suppress warnings on duplicate resource IDs.">; + def verbose : Flag<[ "/", "-" ], "V">, HelpText<"Be verbose.">; def help : Flag<[ "/", "-" ], "?">, HelpText<"Display this help and exit.">; def h : Flag<[ "/", "-" ], "H">, Alias<help>, - HelpText<"Display this help and exit.">; - + HelpText<"Display this help and exit.">; + def dry_run : Flag<[ "/", "-" ], "dry-run">, - HelpText<"Don't compile the input; only try to parse it.">; - + HelpText<"Don't compile the input; only try to parse it.">; + def codepage : JoinedOrSeparate<[ "/", "-" ], "C">, - HelpText<"Set the codepage used for input strings.">; - -// Unused switches (at least for now). These will stay unimplemented -// in an early stage of development and can be ignored. However, we need to -// parse them in order to preserve the compatibility with the original tool. - + HelpText<"Set the codepage used for input strings.">; + +// Unused switches (at least for now). These will stay unimplemented +// in an early stage of development and can be ignored. However, we need to +// parse them in order to preserve the compatibility with the original tool. + def nologo : Flag<[ "/", "-" ], "NOLOGO">; def r : Flag<[ "/", "-" ], "R">; def sl : Flag<[ "/", "-" ], "SL">; - -// (Codepages support.) + +// (Codepages support.) def w : Flag<[ "/", "-" ], "W">; - -// (Support of MUI and similar.) + +// (Support of MUI and similar.) def fm : Separate<[ "/", "-" ], "FM">; def q : Separate<[ "/", "-" ], "Q">; def g : Flag<[ "/", "-" ], "G">; diff --git a/contrib/libs/llvm12/tools/llvm-rc/ResourceFileWriter.cpp b/contrib/libs/llvm12/tools/llvm-rc/ResourceFileWriter.cpp index a7418ba2f1..553bb754ae 100644 --- a/contrib/libs/llvm12/tools/llvm-rc/ResourceFileWriter.cpp +++ b/contrib/libs/llvm12/tools/llvm-rc/ResourceFileWriter.cpp @@ -1,1519 +1,1519 @@ -//===-- ResourceFileWriter.cpp --------------------------------*- C++-*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===---------------------------------------------------------------------===// -// -// This implements the visitor serializing resources to a .res stream. -// -//===---------------------------------------------------------------------===// - -#include "ResourceFileWriter.h" -#include "llvm/Object/WindowsResource.h" -#include "llvm/Support/ConvertUTF.h" -#include "llvm/Support/Endian.h" -#include "llvm/Support/EndianStream.h" -#include "llvm/Support/FileSystem.h" -#include "llvm/Support/MemoryBuffer.h" -#include "llvm/Support/Path.h" -#include "llvm/Support/Process.h" - -using namespace llvm::support; - -// Take an expression returning llvm::Error and forward the error if it exists. -#define RETURN_IF_ERROR(Expr) \ - if (auto Err = (Expr)) \ - return Err; - -namespace llvm { -namespace rc { - -// Class that employs RAII to save the current FileWriter object state -// and revert to it as soon as we leave the scope. This is useful if resources -// declare their own resource-local statements. -class ContextKeeper { - ResourceFileWriter *FileWriter; - ResourceFileWriter::ObjectInfo SavedInfo; - -public: - ContextKeeper(ResourceFileWriter *V) - : FileWriter(V), SavedInfo(V->ObjectData) {} - ~ContextKeeper() { FileWriter->ObjectData = SavedInfo; } -}; - -static Error createError(const Twine &Message, - std::errc Type = std::errc::invalid_argument) { - return make_error<StringError>(Message, std::make_error_code(Type)); -} - -static Error checkNumberFits(uint32_t Number, size_t MaxBits, - const Twine &FieldName) { - assert(1 <= MaxBits && MaxBits <= 32); - if (!(Number >> MaxBits)) - return Error::success(); - return createError(FieldName + " (" + Twine(Number) + ") does not fit in " + - Twine(MaxBits) + " bits.", - std::errc::value_too_large); -} - -template <typename FitType> -static Error checkNumberFits(uint32_t Number, const Twine &FieldName) { - return checkNumberFits(Number, sizeof(FitType) * 8, FieldName); -} - -// A similar function for signed integers. -template <typename FitType> -static Error checkSignedNumberFits(uint32_t Number, const Twine &FieldName, - bool CanBeNegative) { - int32_t SignedNum = Number; - if (SignedNum < std::numeric_limits<FitType>::min() || - SignedNum > std::numeric_limits<FitType>::max()) - return createError(FieldName + " (" + Twine(SignedNum) + - ") does not fit in " + Twine(sizeof(FitType) * 8) + - "-bit signed integer type.", - std::errc::value_too_large); - - if (!CanBeNegative && SignedNum < 0) - return createError(FieldName + " (" + Twine(SignedNum) + - ") cannot be negative."); - - return Error::success(); -} - -static Error checkRCInt(RCInt Number, const Twine &FieldName) { - if (Number.isLong()) - return Error::success(); - return checkNumberFits<uint16_t>(Number, FieldName); -} - -static Error checkIntOrString(IntOrString Value, const Twine &FieldName) { - if (!Value.isInt()) - return Error::success(); - return checkNumberFits<uint16_t>(Value.getInt(), FieldName); -} - -static bool stripQuotes(StringRef &Str, bool &IsLongString) { - if (!Str.contains('"')) - return false; - - // Just take the contents of the string, checking if it's been marked long. - IsLongString = Str.startswith_lower("L"); - if (IsLongString) - Str = Str.drop_front(); - - bool StripSuccess = Str.consume_front("\"") && Str.consume_back("\""); - (void)StripSuccess; - assert(StripSuccess && "Strings should be enclosed in quotes."); - return true; -} - -static UTF16 cp1252ToUnicode(unsigned char C) { - static const UTF16 Map80[] = { - 0x20ac, 0x0081, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, - 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008d, 0x017d, 0x008f, - 0x0090, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, - 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x009d, 0x017e, 0x0178, - }; - if (C >= 0x80 && C <= 0x9F) - return Map80[C - 0x80]; - return C; -} - -// Describes a way to handle '\0' characters when processing the string. -// rc.exe tool sometimes behaves in a weird way in postprocessing. -// If the string to be output is equivalent to a C-string (e.g. in MENU -// titles), string is (predictably) truncated after first 0-byte. -// When outputting a string table, the behavior is equivalent to appending -// '\0\0' at the end of the string, and then stripping the string -// before the first '\0\0' occurrence. -// Finally, when handling strings in user-defined resources, 0-bytes -// aren't stripped, nor do they terminate the string. - -enum class NullHandlingMethod { - UserResource, // Don't terminate string on '\0'. - CutAtNull, // Terminate string on '\0'. - CutAtDoubleNull // Terminate string on '\0\0'; strip final '\0'. -}; - -// Parses an identifier or string and returns a processed version of it: +//===-- ResourceFileWriter.cpp --------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// +// +// This implements the visitor serializing resources to a .res stream. +// +//===---------------------------------------------------------------------===// + +#include "ResourceFileWriter.h" +#include "llvm/Object/WindowsResource.h" +#include "llvm/Support/ConvertUTF.h" +#include "llvm/Support/Endian.h" +#include "llvm/Support/EndianStream.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Process.h" + +using namespace llvm::support; + +// Take an expression returning llvm::Error and forward the error if it exists. +#define RETURN_IF_ERROR(Expr) \ + if (auto Err = (Expr)) \ + return Err; + +namespace llvm { +namespace rc { + +// Class that employs RAII to save the current FileWriter object state +// and revert to it as soon as we leave the scope. This is useful if resources +// declare their own resource-local statements. +class ContextKeeper { + ResourceFileWriter *FileWriter; + ResourceFileWriter::ObjectInfo SavedInfo; + +public: + ContextKeeper(ResourceFileWriter *V) + : FileWriter(V), SavedInfo(V->ObjectData) {} + ~ContextKeeper() { FileWriter->ObjectData = SavedInfo; } +}; + +static Error createError(const Twine &Message, + std::errc Type = std::errc::invalid_argument) { + return make_error<StringError>(Message, std::make_error_code(Type)); +} + +static Error checkNumberFits(uint32_t Number, size_t MaxBits, + const Twine &FieldName) { + assert(1 <= MaxBits && MaxBits <= 32); + if (!(Number >> MaxBits)) + return Error::success(); + return createError(FieldName + " (" + Twine(Number) + ") does not fit in " + + Twine(MaxBits) + " bits.", + std::errc::value_too_large); +} + +template <typename FitType> +static Error checkNumberFits(uint32_t Number, const Twine &FieldName) { + return checkNumberFits(Number, sizeof(FitType) * 8, FieldName); +} + +// A similar function for signed integers. +template <typename FitType> +static Error checkSignedNumberFits(uint32_t Number, const Twine &FieldName, + bool CanBeNegative) { + int32_t SignedNum = Number; + if (SignedNum < std::numeric_limits<FitType>::min() || + SignedNum > std::numeric_limits<FitType>::max()) + return createError(FieldName + " (" + Twine(SignedNum) + + ") does not fit in " + Twine(sizeof(FitType) * 8) + + "-bit signed integer type.", + std::errc::value_too_large); + + if (!CanBeNegative && SignedNum < 0) + return createError(FieldName + " (" + Twine(SignedNum) + + ") cannot be negative."); + + return Error::success(); +} + +static Error checkRCInt(RCInt Number, const Twine &FieldName) { + if (Number.isLong()) + return Error::success(); + return checkNumberFits<uint16_t>(Number, FieldName); +} + +static Error checkIntOrString(IntOrString Value, const Twine &FieldName) { + if (!Value.isInt()) + return Error::success(); + return checkNumberFits<uint16_t>(Value.getInt(), FieldName); +} + +static bool stripQuotes(StringRef &Str, bool &IsLongString) { + if (!Str.contains('"')) + return false; + + // Just take the contents of the string, checking if it's been marked long. + IsLongString = Str.startswith_lower("L"); + if (IsLongString) + Str = Str.drop_front(); + + bool StripSuccess = Str.consume_front("\"") && Str.consume_back("\""); + (void)StripSuccess; + assert(StripSuccess && "Strings should be enclosed in quotes."); + return true; +} + +static UTF16 cp1252ToUnicode(unsigned char C) { + static const UTF16 Map80[] = { + 0x20ac, 0x0081, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, + 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008d, 0x017d, 0x008f, + 0x0090, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x009d, 0x017e, 0x0178, + }; + if (C >= 0x80 && C <= 0x9F) + return Map80[C - 0x80]; + return C; +} + +// Describes a way to handle '\0' characters when processing the string. +// rc.exe tool sometimes behaves in a weird way in postprocessing. +// If the string to be output is equivalent to a C-string (e.g. in MENU +// titles), string is (predictably) truncated after first 0-byte. +// When outputting a string table, the behavior is equivalent to appending +// '\0\0' at the end of the string, and then stripping the string +// before the first '\0\0' occurrence. +// Finally, when handling strings in user-defined resources, 0-bytes +// aren't stripped, nor do they terminate the string. + +enum class NullHandlingMethod { + UserResource, // Don't terminate string on '\0'. + CutAtNull, // Terminate string on '\0'. + CutAtDoubleNull // Terminate string on '\0\0'; strip final '\0'. +}; + +// Parses an identifier or string and returns a processed version of it: // * Strip the string boundary quotes. // * Convert the input code page characters to UTF16. -// * Squash "" to a single ". -// * Replace the escape sequences with their processed version. -// For identifiers, this is no-op. -static Error processString(StringRef Str, NullHandlingMethod NullHandler, - bool &IsLongString, SmallVectorImpl<UTF16> &Result, - int CodePage) { - bool IsString = stripQuotes(Str, IsLongString); - SmallVector<UTF16, 128> Chars; - - // Convert the input bytes according to the chosen codepage. - if (CodePage == CpUtf8) { - convertUTF8ToUTF16String(Str, Chars); - } else if (CodePage == CpWin1252) { - for (char C : Str) - Chars.push_back(cp1252ToUnicode((unsigned char)C)); - } else { - // For other, unknown codepages, only allow plain ASCII input. - for (char C : Str) { - if ((unsigned char)C > 0x7F) - return createError("Non-ASCII 8-bit codepoint (" + Twine(C) + - ") can't be interpreted in the current codepage"); - Chars.push_back((unsigned char)C); - } - } - - if (!IsString) { - // It's an identifier if it's not a string. Make all characters uppercase. - for (UTF16 &Ch : Chars) { - assert(Ch <= 0x7F && "We didn't allow identifiers to be non-ASCII"); - Ch = toupper(Ch); - } - Result.swap(Chars); - return Error::success(); - } - Result.reserve(Chars.size()); - size_t Pos = 0; - - auto AddRes = [&Result, NullHandler, IsLongString](UTF16 Char) -> Error { - if (!IsLongString) { - if (NullHandler == NullHandlingMethod::UserResource) { - // Narrow strings in user-defined resources are *not* output in - // UTF-16 format. - if (Char > 0xFF) - return createError("Non-8-bit codepoint (" + Twine(Char) + - ") can't occur in a user-defined narrow string"); - } - } - - Result.push_back(Char); - return Error::success(); - }; - auto AddEscapedChar = [AddRes, IsLongString, CodePage](UTF16 Char) -> Error { - if (!IsLongString) { - // Escaped chars in narrow strings have to be interpreted according to - // the chosen code page. - if (Char > 0xFF) - return createError("Non-8-bit escaped char (" + Twine(Char) + - ") can't occur in narrow string"); - if (CodePage == CpUtf8) { - if (Char >= 0x80) - return createError("Unable to interpret single byte (" + Twine(Char) + - ") as UTF-8"); - } else if (CodePage == CpWin1252) { - Char = cp1252ToUnicode(Char); - } else { - // Unknown/unsupported codepage, only allow ASCII input. - if (Char > 0x7F) - return createError("Non-ASCII 8-bit codepoint (" + Twine(Char) + - ") can't " - "occur in a non-Unicode string"); - } - } - - return AddRes(Char); - }; - - while (Pos < Chars.size()) { - UTF16 CurChar = Chars[Pos]; - ++Pos; - - // Strip double "". - if (CurChar == '"') { - if (Pos == Chars.size() || Chars[Pos] != '"') - return createError("Expected \"\""); - ++Pos; - RETURN_IF_ERROR(AddRes('"')); - continue; - } - - if (CurChar == '\\') { - UTF16 TypeChar = Chars[Pos]; - ++Pos; - - if (TypeChar == 'x' || TypeChar == 'X') { - // Read a hex number. Max number of characters to read differs between - // narrow and wide strings. - UTF16 ReadInt = 0; - size_t RemainingChars = IsLongString ? 4 : 2; - // We don't want to read non-ASCII hex digits. std:: functions past - // 0xFF invoke UB. - // - // FIXME: actually, Microsoft version probably doesn't check this - // condition and uses their Unicode version of 'isxdigit'. However, - // there are some hex-digit Unicode character outside of ASCII, and - // some of these are actually accepted by rc.exe, the notable example - // being fullwidth forms (U+FF10..U+FF19 etc.) These can be written - // instead of ASCII digits in \x... escape sequence and get accepted. - // However, the resulting hexcodes seem totally unpredictable. - // We think it's infeasible to try to reproduce this behavior, nor to - // put effort in order to detect it. - while (RemainingChars && Pos < Chars.size() && Chars[Pos] < 0x80) { - if (!isxdigit(Chars[Pos])) - break; - char Digit = tolower(Chars[Pos]); - ++Pos; - - ReadInt <<= 4; - if (isdigit(Digit)) - ReadInt |= Digit - '0'; - else - ReadInt |= Digit - 'a' + 10; - - --RemainingChars; - } - - RETURN_IF_ERROR(AddEscapedChar(ReadInt)); - continue; - } - - if (TypeChar >= '0' && TypeChar < '8') { - // Read an octal number. Note that we've already read the first digit. - UTF16 ReadInt = TypeChar - '0'; - size_t RemainingChars = IsLongString ? 6 : 2; - - while (RemainingChars && Pos < Chars.size() && Chars[Pos] >= '0' && - Chars[Pos] < '8') { - ReadInt <<= 3; - ReadInt |= Chars[Pos] - '0'; - --RemainingChars; - ++Pos; - } - - RETURN_IF_ERROR(AddEscapedChar(ReadInt)); - - continue; - } - - switch (TypeChar) { - case 'A': - case 'a': - // Windows '\a' translates into '\b' (Backspace). - RETURN_IF_ERROR(AddRes('\b')); - break; - - case 'n': // Somehow, RC doesn't recognize '\N' and '\R'. - RETURN_IF_ERROR(AddRes('\n')); - break; - - case 'r': - RETURN_IF_ERROR(AddRes('\r')); - break; - - case 'T': - case 't': - RETURN_IF_ERROR(AddRes('\t')); - break; - - case '\\': - RETURN_IF_ERROR(AddRes('\\')); - break; - - case '"': - // RC accepts \" only if another " comes afterwards; then, \"" means - // a single ". - if (Pos == Chars.size() || Chars[Pos] != '"') - return createError("Expected \\\"\""); - ++Pos; - RETURN_IF_ERROR(AddRes('"')); - break; - - default: - // If TypeChar means nothing, \ is should be output to stdout with - // following char. However, rc.exe consumes these characters when - // dealing with wide strings. - if (!IsLongString) { - RETURN_IF_ERROR(AddRes('\\')); - RETURN_IF_ERROR(AddRes(TypeChar)); - } - break; - } - - continue; - } - - // If nothing interesting happens, just output the character. - RETURN_IF_ERROR(AddRes(CurChar)); - } - - switch (NullHandler) { - case NullHandlingMethod::CutAtNull: - for (size_t Pos = 0; Pos < Result.size(); ++Pos) - if (Result[Pos] == '\0') - Result.resize(Pos); - break; - - case NullHandlingMethod::CutAtDoubleNull: - for (size_t Pos = 0; Pos + 1 < Result.size(); ++Pos) - if (Result[Pos] == '\0' && Result[Pos + 1] == '\0') - Result.resize(Pos); - if (Result.size() > 0 && Result.back() == '\0') - Result.pop_back(); - break; - - case NullHandlingMethod::UserResource: - break; - } - - return Error::success(); -} - -uint64_t ResourceFileWriter::writeObject(const ArrayRef<uint8_t> Data) { - uint64_t Result = tell(); - FS->write((const char *)Data.begin(), Data.size()); - return Result; -} - -Error ResourceFileWriter::writeCString(StringRef Str, bool WriteTerminator) { - SmallVector<UTF16, 128> ProcessedString; - bool IsLongString; - RETURN_IF_ERROR(processString(Str, NullHandlingMethod::CutAtNull, - IsLongString, ProcessedString, - Params.CodePage)); - for (auto Ch : ProcessedString) - writeInt<uint16_t>(Ch); - if (WriteTerminator) - writeInt<uint16_t>(0); - return Error::success(); -} - -Error ResourceFileWriter::writeIdentifier(const IntOrString &Ident) { - return writeIntOrString(Ident); -} - -Error ResourceFileWriter::writeIntOrString(const IntOrString &Value) { - if (!Value.isInt()) - return writeCString(Value.getString()); - - writeInt<uint16_t>(0xFFFF); - writeInt<uint16_t>(Value.getInt()); - return Error::success(); -} - -void ResourceFileWriter::writeRCInt(RCInt Value) { - if (Value.isLong()) - writeInt<uint32_t>(Value); - else - writeInt<uint16_t>(Value); -} - -Error ResourceFileWriter::appendFile(StringRef Filename) { - bool IsLong; - stripQuotes(Filename, IsLong); - - auto File = loadFile(Filename); - if (!File) - return File.takeError(); - - *FS << (*File)->getBuffer(); - return Error::success(); -} - -void ResourceFileWriter::padStream(uint64_t Length) { - assert(Length > 0); - uint64_t Location = tell(); - Location %= Length; - uint64_t Pad = (Length - Location) % Length; - for (uint64_t i = 0; i < Pad; ++i) - writeInt<uint8_t>(0); -} - -Error ResourceFileWriter::handleError(Error Err, const RCResource *Res) { - if (Err) - return joinErrors(createError("Error in " + Res->getResourceTypeName() + - " statement (ID " + Twine(Res->ResName) + - "): "), - std::move(Err)); - return Error::success(); -} - -Error ResourceFileWriter::visitNullResource(const RCResource *Res) { - return writeResource(Res, &ResourceFileWriter::writeNullBody); -} - -Error ResourceFileWriter::visitAcceleratorsResource(const RCResource *Res) { - return writeResource(Res, &ResourceFileWriter::writeAcceleratorsBody); -} - -Error ResourceFileWriter::visitBitmapResource(const RCResource *Res) { - return writeResource(Res, &ResourceFileWriter::writeBitmapBody); -} - -Error ResourceFileWriter::visitCursorResource(const RCResource *Res) { - return handleError(visitIconOrCursorResource(Res), Res); -} - -Error ResourceFileWriter::visitDialogResource(const RCResource *Res) { - return writeResource(Res, &ResourceFileWriter::writeDialogBody); -} - -Error ResourceFileWriter::visitIconResource(const RCResource *Res) { - return handleError(visitIconOrCursorResource(Res), Res); -} - -Error ResourceFileWriter::visitCaptionStmt(const CaptionStmt *Stmt) { - ObjectData.Caption = Stmt->Value; - return Error::success(); -} - -Error ResourceFileWriter::visitClassStmt(const ClassStmt *Stmt) { - ObjectData.Class = Stmt->Value; - return Error::success(); -} - -Error ResourceFileWriter::visitHTMLResource(const RCResource *Res) { - return writeResource(Res, &ResourceFileWriter::writeHTMLBody); -} - -Error ResourceFileWriter::visitMenuResource(const RCResource *Res) { - return writeResource(Res, &ResourceFileWriter::writeMenuBody); -} - -Error ResourceFileWriter::visitStringTableResource(const RCResource *Base) { - const auto *Res = cast<StringTableResource>(Base); - - ContextKeeper RAII(this); - RETURN_IF_ERROR(Res->applyStmts(this)); - - for (auto &String : Res->Table) { - RETURN_IF_ERROR(checkNumberFits<uint16_t>(String.first, "String ID")); - uint16_t BundleID = String.first >> 4; - StringTableInfo::BundleKey Key(BundleID, ObjectData.LanguageInfo); - auto &BundleData = StringTableData.BundleData; - auto Iter = BundleData.find(Key); - - if (Iter == BundleData.end()) { - // Need to create a bundle. - StringTableData.BundleList.push_back(Key); - auto EmplaceResult = BundleData.emplace( - Key, StringTableInfo::Bundle(ObjectData, Res->MemoryFlags)); - assert(EmplaceResult.second && "Could not create a bundle"); - Iter = EmplaceResult.first; - } - - RETURN_IF_ERROR( - insertStringIntoBundle(Iter->second, String.first, String.second)); - } - - return Error::success(); -} - -Error ResourceFileWriter::visitUserDefinedResource(const RCResource *Res) { - return writeResource(Res, &ResourceFileWriter::writeUserDefinedBody); -} - -Error ResourceFileWriter::visitVersionInfoResource(const RCResource *Res) { - return writeResource(Res, &ResourceFileWriter::writeVersionInfoBody); -} - -Error ResourceFileWriter::visitCharacteristicsStmt( - const CharacteristicsStmt *Stmt) { - ObjectData.Characteristics = Stmt->Value; - return Error::success(); -} - -Error ResourceFileWriter::visitExStyleStmt(const ExStyleStmt *Stmt) { - ObjectData.ExStyle = Stmt->Value; - return Error::success(); -} - -Error ResourceFileWriter::visitFontStmt(const FontStmt *Stmt) { - RETURN_IF_ERROR(checkNumberFits<uint16_t>(Stmt->Size, "Font size")); - RETURN_IF_ERROR(checkNumberFits<uint16_t>(Stmt->Weight, "Font weight")); - RETURN_IF_ERROR(checkNumberFits<uint8_t>(Stmt->Charset, "Font charset")); - ObjectInfo::FontInfo Font{Stmt->Size, Stmt->Name, Stmt->Weight, Stmt->Italic, - Stmt->Charset}; - ObjectData.Font.emplace(Font); - return Error::success(); -} - -Error ResourceFileWriter::visitLanguageStmt(const LanguageResource *Stmt) { - RETURN_IF_ERROR(checkNumberFits(Stmt->Lang, 10, "Primary language ID")); - RETURN_IF_ERROR(checkNumberFits(Stmt->SubLang, 6, "Sublanguage ID")); - ObjectData.LanguageInfo = Stmt->Lang | (Stmt->SubLang << 10); - return Error::success(); -} - -Error ResourceFileWriter::visitStyleStmt(const StyleStmt *Stmt) { - ObjectData.Style = Stmt->Value; - return Error::success(); -} - -Error ResourceFileWriter::visitVersionStmt(const VersionStmt *Stmt) { - ObjectData.VersionInfo = Stmt->Value; - return Error::success(); -} - -Error ResourceFileWriter::writeResource( - const RCResource *Res, - Error (ResourceFileWriter::*BodyWriter)(const RCResource *)) { - // We don't know the sizes yet. - object::WinResHeaderPrefix HeaderPrefix{ulittle32_t(0U), ulittle32_t(0U)}; - uint64_t HeaderLoc = writeObject(HeaderPrefix); - - auto ResType = Res->getResourceType(); - RETURN_IF_ERROR(checkIntOrString(ResType, "Resource type")); - RETURN_IF_ERROR(checkIntOrString(Res->ResName, "Resource ID")); - RETURN_IF_ERROR(handleError(writeIdentifier(ResType), Res)); - RETURN_IF_ERROR(handleError(writeIdentifier(Res->ResName), Res)); - - // Apply the resource-local optional statements. - ContextKeeper RAII(this); - RETURN_IF_ERROR(handleError(Res->applyStmts(this), Res)); - - padStream(sizeof(uint32_t)); - object::WinResHeaderSuffix HeaderSuffix{ - ulittle32_t(0), // DataVersion; seems to always be 0 - ulittle16_t(Res->MemoryFlags), ulittle16_t(ObjectData.LanguageInfo), - ulittle32_t(ObjectData.VersionInfo), - ulittle32_t(ObjectData.Characteristics)}; - writeObject(HeaderSuffix); - - uint64_t DataLoc = tell(); - RETURN_IF_ERROR(handleError((this->*BodyWriter)(Res), Res)); - // RETURN_IF_ERROR(handleError(dumpResource(Ctx))); - - // Update the sizes. - HeaderPrefix.DataSize = tell() - DataLoc; - HeaderPrefix.HeaderSize = DataLoc - HeaderLoc; - writeObjectAt(HeaderPrefix, HeaderLoc); - padStream(sizeof(uint32_t)); - - return Error::success(); -} - -// --- NullResource helpers. --- // - -Error ResourceFileWriter::writeNullBody(const RCResource *) { - return Error::success(); -} - -// --- AcceleratorsResource helpers. --- // - -Error ResourceFileWriter::writeSingleAccelerator( - const AcceleratorsResource::Accelerator &Obj, bool IsLastItem) { - using Accelerator = AcceleratorsResource::Accelerator; - using Opt = Accelerator::Options; - - struct AccelTableEntry { - ulittle16_t Flags; - ulittle16_t ANSICode; - ulittle16_t Id; - uint16_t Padding; - } Entry{ulittle16_t(0), ulittle16_t(0), ulittle16_t(0), 0}; - - bool IsASCII = Obj.Flags & Opt::ASCII, IsVirtKey = Obj.Flags & Opt::VIRTKEY; - - // Remove ASCII flags (which doesn't occur in .res files). - Entry.Flags = Obj.Flags & ~Opt::ASCII; - - if (IsLastItem) - Entry.Flags |= 0x80; - - RETURN_IF_ERROR(checkNumberFits<uint16_t>(Obj.Id, "ACCELERATORS entry ID")); - Entry.Id = ulittle16_t(Obj.Id); - - auto createAccError = [&Obj](const char *Msg) { - return createError("Accelerator ID " + Twine(Obj.Id) + ": " + Msg); - }; - - if (IsASCII && IsVirtKey) - return createAccError("Accelerator can't be both ASCII and VIRTKEY"); - - if (!IsVirtKey && (Obj.Flags & (Opt::ALT | Opt::SHIFT | Opt::CONTROL))) - return createAccError("Can only apply ALT, SHIFT or CONTROL to VIRTKEY" - " accelerators"); - - if (Obj.Event.isInt()) { - if (!IsASCII && !IsVirtKey) - return createAccError( - "Accelerator with a numeric event must be either ASCII" - " or VIRTKEY"); - - uint32_t EventVal = Obj.Event.getInt(); - RETURN_IF_ERROR( - checkNumberFits<uint16_t>(EventVal, "Numeric event key ID")); - Entry.ANSICode = ulittle16_t(EventVal); - writeObject(Entry); - return Error::success(); - } - - StringRef Str = Obj.Event.getString(); - bool IsWide; - stripQuotes(Str, IsWide); - - if (Str.size() == 0 || Str.size() > 2) - return createAccError( - "Accelerator string events should have length 1 or 2"); - - if (Str[0] == '^') { - if (Str.size() == 1) - return createAccError("No character following '^' in accelerator event"); - if (IsVirtKey) - return createAccError( - "VIRTKEY accelerator events can't be preceded by '^'"); - - char Ch = Str[1]; - if (Ch >= 'a' && Ch <= 'z') - Entry.ANSICode = ulittle16_t(Ch - 'a' + 1); - else if (Ch >= 'A' && Ch <= 'Z') - Entry.ANSICode = ulittle16_t(Ch - 'A' + 1); - else - return createAccError("Control character accelerator event should be" - " alphabetic"); - - writeObject(Entry); - return Error::success(); - } - - if (Str.size() == 2) - return createAccError("Event string should be one-character, possibly" - " preceded by '^'"); - - uint8_t EventCh = Str[0]; - // The original tool just warns in this situation. We chose to fail. - if (IsVirtKey && !isalnum(EventCh)) - return createAccError("Non-alphanumeric characters cannot describe virtual" - " keys"); - if (EventCh > 0x7F) - return createAccError("Non-ASCII description of accelerator"); - - if (IsVirtKey) - EventCh = toupper(EventCh); - Entry.ANSICode = ulittle16_t(EventCh); - writeObject(Entry); - return Error::success(); -} - -Error ResourceFileWriter::writeAcceleratorsBody(const RCResource *Base) { - auto *Res = cast<AcceleratorsResource>(Base); - size_t AcceleratorId = 0; - for (auto &Acc : Res->Accelerators) { - ++AcceleratorId; - RETURN_IF_ERROR( - writeSingleAccelerator(Acc, AcceleratorId == Res->Accelerators.size())); - } - return Error::success(); -} - -// --- BitmapResource helpers. --- // - -Error ResourceFileWriter::writeBitmapBody(const RCResource *Base) { - StringRef Filename = cast<BitmapResource>(Base)->BitmapLoc; - bool IsLong; - stripQuotes(Filename, IsLong); - - auto File = loadFile(Filename); - if (!File) - return File.takeError(); - - StringRef Buffer = (*File)->getBuffer(); - - // Skip the 14 byte BITMAPFILEHEADER. - constexpr size_t BITMAPFILEHEADER_size = 14; - if (Buffer.size() < BITMAPFILEHEADER_size || Buffer[0] != 'B' || - Buffer[1] != 'M') - return createError("Incorrect bitmap file."); - - *FS << Buffer.substr(BITMAPFILEHEADER_size); - return Error::success(); -} - -// --- CursorResource and IconResource helpers. --- // - -// ICONRESDIR structure. Describes a single icon in resource group. -// -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648016.aspx -struct IconResDir { - uint8_t Width; - uint8_t Height; - uint8_t ColorCount; - uint8_t Reserved; -}; - -// CURSORDIR structure. Describes a single cursor in resource group. -// -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648011(v=vs.85).aspx -struct CursorDir { - ulittle16_t Width; - ulittle16_t Height; -}; - -// RESDIRENTRY structure, stripped from the last item. Stripping made -// for compatibility with RESDIR. -// -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026(v=vs.85).aspx -struct ResourceDirEntryStart { - union { - CursorDir Cursor; // Used in CURSOR resources. - IconResDir Icon; // Used in .ico and .cur files, and ICON resources. - }; - ulittle16_t Planes; // HotspotX (.cur files but not CURSOR resource). - ulittle16_t BitCount; // HotspotY (.cur files but not CURSOR resource). - ulittle32_t Size; - // ulittle32_t ImageOffset; // Offset to image data (ICONDIRENTRY only). - // ulittle16_t IconID; // Resource icon ID (RESDIR only). -}; - -// BITMAPINFOHEADER structure. Describes basic information about the bitmap -// being read. -// -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx -struct BitmapInfoHeader { - ulittle32_t Size; - ulittle32_t Width; - ulittle32_t Height; - ulittle16_t Planes; - ulittle16_t BitCount; - ulittle32_t Compression; - ulittle32_t SizeImage; - ulittle32_t XPelsPerMeter; - ulittle32_t YPelsPerMeter; - ulittle32_t ClrUsed; - ulittle32_t ClrImportant; -}; - -// Group icon directory header. Called ICONDIR in .ico/.cur files and -// NEWHEADER in .res files. -// -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648023(v=vs.85).aspx -struct GroupIconDir { - ulittle16_t Reserved; // Always 0. - ulittle16_t ResType; // 1 for icons, 2 for cursors. - ulittle16_t ResCount; // Number of items. -}; - -enum class IconCursorGroupType { Icon, Cursor }; - -class SingleIconCursorResource : public RCResource { -public: - IconCursorGroupType Type; - const ResourceDirEntryStart &Header; - ArrayRef<uint8_t> Image; - - SingleIconCursorResource(IconCursorGroupType ResourceType, - const ResourceDirEntryStart &HeaderEntry, - ArrayRef<uint8_t> ImageData, uint16_t Flags) - : RCResource(Flags), Type(ResourceType), Header(HeaderEntry), - Image(ImageData) {} - - Twine getResourceTypeName() const override { return "Icon/cursor image"; } - IntOrString getResourceType() const override { - return Type == IconCursorGroupType::Icon ? RkSingleIcon : RkSingleCursor; - } - ResourceKind getKind() const override { return RkSingleCursorOrIconRes; } - static bool classof(const RCResource *Res) { - return Res->getKind() == RkSingleCursorOrIconRes; - } -}; - -class IconCursorGroupResource : public RCResource { -public: - IconCursorGroupType Type; - GroupIconDir Header; - std::vector<ResourceDirEntryStart> ItemEntries; - - IconCursorGroupResource(IconCursorGroupType ResourceType, - const GroupIconDir &HeaderData, - std::vector<ResourceDirEntryStart> &&Entries) - : Type(ResourceType), Header(HeaderData), - ItemEntries(std::move(Entries)) {} - - Twine getResourceTypeName() const override { return "Icon/cursor group"; } - IntOrString getResourceType() const override { - return Type == IconCursorGroupType::Icon ? RkIconGroup : RkCursorGroup; - } - ResourceKind getKind() const override { return RkCursorOrIconGroupRes; } - static bool classof(const RCResource *Res) { - return Res->getKind() == RkCursorOrIconGroupRes; - } -}; - -Error ResourceFileWriter::writeSingleIconOrCursorBody(const RCResource *Base) { - auto *Res = cast<SingleIconCursorResource>(Base); - if (Res->Type == IconCursorGroupType::Cursor) { - // In case of cursors, two WORDS are appended to the beginning - // of the resource: HotspotX (Planes in RESDIRENTRY), - // and HotspotY (BitCount). - // - // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026.aspx - // (Remarks section). - writeObject(Res->Header.Planes); - writeObject(Res->Header.BitCount); - } - - writeObject(Res->Image); - return Error::success(); -} - -Error ResourceFileWriter::writeIconOrCursorGroupBody(const RCResource *Base) { - auto *Res = cast<IconCursorGroupResource>(Base); - writeObject(Res->Header); - for (auto Item : Res->ItemEntries) { - writeObject(Item); - writeInt(IconCursorID++); - } - return Error::success(); -} - -Error ResourceFileWriter::visitSingleIconOrCursor(const RCResource *Res) { - return writeResource(Res, &ResourceFileWriter::writeSingleIconOrCursorBody); -} - -Error ResourceFileWriter::visitIconOrCursorGroup(const RCResource *Res) { - return writeResource(Res, &ResourceFileWriter::writeIconOrCursorGroupBody); -} - -Error ResourceFileWriter::visitIconOrCursorResource(const RCResource *Base) { - IconCursorGroupType Type; - StringRef FileStr; - IntOrString ResName = Base->ResName; - - if (auto *IconRes = dyn_cast<IconResource>(Base)) { - FileStr = IconRes->IconLoc; - Type = IconCursorGroupType::Icon; - } else { - auto *CursorRes = dyn_cast<CursorResource>(Base); - FileStr = CursorRes->CursorLoc; - Type = IconCursorGroupType::Cursor; - } - - bool IsLong; - stripQuotes(FileStr, IsLong); - auto File = loadFile(FileStr); - - if (!File) - return File.takeError(); - - BinaryStreamReader Reader((*File)->getBuffer(), support::little); - - // Read the file headers. - // - At the beginning, ICONDIR/NEWHEADER header. - // - Then, a number of RESDIR headers follow. These contain offsets - // to data. - const GroupIconDir *Header; - - RETURN_IF_ERROR(Reader.readObject(Header)); - if (Header->Reserved != 0) - return createError("Incorrect icon/cursor Reserved field; should be 0."); - uint16_t NeededType = Type == IconCursorGroupType::Icon ? 1 : 2; - if (Header->ResType != NeededType) - return createError("Incorrect icon/cursor ResType field; should be " + - Twine(NeededType) + "."); - - uint16_t NumItems = Header->ResCount; - - // Read single ico/cur headers. - std::vector<ResourceDirEntryStart> ItemEntries; - ItemEntries.reserve(NumItems); - std::vector<uint32_t> ItemOffsets(NumItems); - for (size_t ID = 0; ID < NumItems; ++ID) { - const ResourceDirEntryStart *Object; - RETURN_IF_ERROR(Reader.readObject(Object)); - ItemEntries.push_back(*Object); - RETURN_IF_ERROR(Reader.readInteger(ItemOffsets[ID])); - } - - // Now write each icon/cursors one by one. At first, all the contents - // without ICO/CUR header. This is described by SingleIconCursorResource. - for (size_t ID = 0; ID < NumItems; ++ID) { - // Load the fragment of file. - Reader.setOffset(ItemOffsets[ID]); - ArrayRef<uint8_t> Image; - RETURN_IF_ERROR(Reader.readArray(Image, ItemEntries[ID].Size)); - SingleIconCursorResource SingleRes(Type, ItemEntries[ID], Image, - Base->MemoryFlags); - SingleRes.setName(IconCursorID + ID); - RETURN_IF_ERROR(visitSingleIconOrCursor(&SingleRes)); - } - - // Now, write all the headers concatenated into a separate resource. - for (size_t ID = 0; ID < NumItems; ++ID) { - // We need to rewrite the cursor headers, and fetch actual values - // for Planes/BitCount. - const auto &OldHeader = ItemEntries[ID]; - ResourceDirEntryStart NewHeader = OldHeader; - - if (Type == IconCursorGroupType::Cursor) { - NewHeader.Cursor.Width = OldHeader.Icon.Width; - // Each cursor in fact stores two bitmaps, one under another. - // Height provided in cursor definition describes the height of the - // cursor, whereas the value existing in resource definition describes - // the height of the bitmap. Therefore, we need to double this height. - NewHeader.Cursor.Height = OldHeader.Icon.Height * 2; - - // Two WORDs were written at the beginning of the resource (hotspot - // location). This is reflected in Size field. - NewHeader.Size += 2 * sizeof(uint16_t); - } - - // Now, we actually need to read the bitmap header to find - // the number of planes and the number of bits per pixel. - Reader.setOffset(ItemOffsets[ID]); - const BitmapInfoHeader *BMPHeader; - RETURN_IF_ERROR(Reader.readObject(BMPHeader)); - if (BMPHeader->Size == sizeof(BitmapInfoHeader)) { - NewHeader.Planes = BMPHeader->Planes; - NewHeader.BitCount = BMPHeader->BitCount; - } else { - // A PNG .ico file. - // https://blogs.msdn.microsoft.com/oldnewthing/20101022-00/?p=12473 - // "The image must be in 32bpp" - NewHeader.Planes = 1; - NewHeader.BitCount = 32; - } - - ItemEntries[ID] = NewHeader; - } - - IconCursorGroupResource HeaderRes(Type, *Header, std::move(ItemEntries)); - HeaderRes.setName(ResName); - if (Base->MemoryFlags & MfPreload) { - HeaderRes.MemoryFlags |= MfPreload; - HeaderRes.MemoryFlags &= ~MfPure; - } - RETURN_IF_ERROR(visitIconOrCursorGroup(&HeaderRes)); - - return Error::success(); -} - -// --- DialogResource helpers. --- // - -Error ResourceFileWriter::writeSingleDialogControl(const Control &Ctl, - bool IsExtended) { - // Each control should be aligned to DWORD. - padStream(sizeof(uint32_t)); - - auto TypeInfo = Control::SupportedCtls.lookup(Ctl.Type); - IntWithNotMask CtlStyle(TypeInfo.Style); - CtlStyle |= Ctl.Style.getValueOr(RCInt(0)); - uint32_t CtlExtStyle = Ctl.ExtStyle.getValueOr(0); - - // DIALOG(EX) item header prefix. - if (!IsExtended) { - struct { - ulittle32_t Style; - ulittle32_t ExtStyle; - } Prefix{ulittle32_t(CtlStyle.getValue()), ulittle32_t(CtlExtStyle)}; - writeObject(Prefix); - } else { - struct { - ulittle32_t HelpID; - ulittle32_t ExtStyle; - ulittle32_t Style; - } Prefix{ulittle32_t(Ctl.HelpID.getValueOr(0)), ulittle32_t(CtlExtStyle), - ulittle32_t(CtlStyle.getValue())}; - writeObject(Prefix); - } - - // Common fixed-length part. - RETURN_IF_ERROR(checkSignedNumberFits<int16_t>( - Ctl.X, "Dialog control x-coordinate", true)); - RETURN_IF_ERROR(checkSignedNumberFits<int16_t>( - Ctl.Y, "Dialog control y-coordinate", true)); - RETURN_IF_ERROR( - checkSignedNumberFits<int16_t>(Ctl.Width, "Dialog control width", false)); - RETURN_IF_ERROR(checkSignedNumberFits<int16_t>( - Ctl.Height, "Dialog control height", false)); - struct { - ulittle16_t X; - ulittle16_t Y; - ulittle16_t Width; - ulittle16_t Height; - } Middle{ulittle16_t(Ctl.X), ulittle16_t(Ctl.Y), ulittle16_t(Ctl.Width), - ulittle16_t(Ctl.Height)}; - writeObject(Middle); - - // ID; it's 16-bit in DIALOG and 32-bit in DIALOGEX. - if (!IsExtended) { - // It's common to use -1, i.e. UINT32_MAX, for controls one doesn't - // want to refer to later. - if (Ctl.ID != static_cast<uint32_t>(-1)) - RETURN_IF_ERROR(checkNumberFits<uint16_t>( - Ctl.ID, "Control ID in simple DIALOG resource")); - writeInt<uint16_t>(Ctl.ID); - } else { - writeInt<uint32_t>(Ctl.ID); - } - - // Window class - either 0xFFFF + 16-bit integer or a string. - RETURN_IF_ERROR(writeIntOrString(Ctl.Class)); - - // Element caption/reference ID. ID is preceded by 0xFFFF. - RETURN_IF_ERROR(checkIntOrString(Ctl.Title, "Control reference ID")); - RETURN_IF_ERROR(writeIntOrString(Ctl.Title)); - - // # bytes of extra creation data count. Don't pass any. - writeInt<uint16_t>(0); - - return Error::success(); -} - -Error ResourceFileWriter::writeDialogBody(const RCResource *Base) { - auto *Res = cast<DialogResource>(Base); - - // Default style: WS_POPUP | WS_BORDER | WS_SYSMENU. - const uint32_t DefaultStyle = 0x80880000; - const uint32_t StyleFontFlag = 0x40; - const uint32_t StyleCaptionFlag = 0x00C00000; - - uint32_t UsedStyle = ObjectData.Style.getValueOr(DefaultStyle); - if (ObjectData.Font) - UsedStyle |= StyleFontFlag; - else - UsedStyle &= ~StyleFontFlag; - - // Actually, in case of empty (but existent) caption, the examined field - // is equal to "\"\"". That's why empty captions are still noticed. - if (ObjectData.Caption != "") - UsedStyle |= StyleCaptionFlag; - - const uint16_t DialogExMagic = 0xFFFF; - uint32_t ExStyle = ObjectData.ExStyle.getValueOr(0); - - // Write DIALOG(EX) header prefix. These are pretty different. - if (!Res->IsExtended) { - // We cannot let the higher word of DefaultStyle be equal to 0xFFFF. - // In such a case, whole object (in .res file) is equivalent to a - // DIALOGEX. It might lead to access violation/segmentation fault in - // resource readers. For example, - // 1 DIALOG 0, 0, 0, 65432 - // STYLE 0xFFFF0001 {} - // would be compiled to a DIALOGEX with 65432 controls. - if ((UsedStyle >> 16) == DialogExMagic) - return createError("16 higher bits of DIALOG resource style cannot be" - " equal to 0xFFFF"); - - struct { - ulittle32_t Style; - ulittle32_t ExtStyle; - } Prefix{ulittle32_t(UsedStyle), - ulittle32_t(ExStyle)}; - - writeObject(Prefix); - } else { - struct { - ulittle16_t Version; - ulittle16_t Magic; - ulittle32_t HelpID; - ulittle32_t ExtStyle; - ulittle32_t Style; - } Prefix{ulittle16_t(1), ulittle16_t(DialogExMagic), - ulittle32_t(Res->HelpID), ulittle32_t(ExStyle), ulittle32_t(UsedStyle)}; - - writeObject(Prefix); - } - - // Now, a common part. First, fixed-length fields. - RETURN_IF_ERROR(checkNumberFits<uint16_t>(Res->Controls.size(), - "Number of dialog controls")); - RETURN_IF_ERROR( - checkSignedNumberFits<int16_t>(Res->X, "Dialog x-coordinate", true)); - RETURN_IF_ERROR( - checkSignedNumberFits<int16_t>(Res->Y, "Dialog y-coordinate", true)); - RETURN_IF_ERROR( - checkSignedNumberFits<int16_t>(Res->Width, "Dialog width", false)); - RETURN_IF_ERROR( - checkSignedNumberFits<int16_t>(Res->Height, "Dialog height", false)); - struct { - ulittle16_t Count; - ulittle16_t PosX; - ulittle16_t PosY; - ulittle16_t DialogWidth; - ulittle16_t DialogHeight; - } Middle{ulittle16_t(Res->Controls.size()), ulittle16_t(Res->X), - ulittle16_t(Res->Y), ulittle16_t(Res->Width), - ulittle16_t(Res->Height)}; - writeObject(Middle); - - // MENU field. As of now, we don't keep them in the state and can peacefully - // think there is no menu attached to the dialog. - writeInt<uint16_t>(0); - - // Window CLASS field. - RETURN_IF_ERROR(writeIntOrString(ObjectData.Class)); - - // Window title or a single word equal to 0. - RETURN_IF_ERROR(writeCString(ObjectData.Caption)); - - // If there *is* a window font declared, output its data. - auto &Font = ObjectData.Font; - if (Font) { - writeInt<uint16_t>(Font->Size); - // Additional description occurs only in DIALOGEX. - if (Res->IsExtended) { - writeInt<uint16_t>(Font->Weight); - writeInt<uint8_t>(Font->IsItalic); - writeInt<uint8_t>(Font->Charset); - } - RETURN_IF_ERROR(writeCString(Font->Typeface)); - } - - auto handleCtlError = [&](Error &&Err, const Control &Ctl) -> Error { - if (!Err) - return Error::success(); - return joinErrors(createError("Error in " + Twine(Ctl.Type) + - " control (ID " + Twine(Ctl.ID) + "):"), - std::move(Err)); - }; - - for (auto &Ctl : Res->Controls) - RETURN_IF_ERROR( - handleCtlError(writeSingleDialogControl(Ctl, Res->IsExtended), Ctl)); - - return Error::success(); -} - -// --- HTMLResource helpers. --- // - -Error ResourceFileWriter::writeHTMLBody(const RCResource *Base) { - return appendFile(cast<HTMLResource>(Base)->HTMLLoc); -} - -// --- MenuResource helpers. --- // - -Error ResourceFileWriter::writeMenuDefinition( - const std::unique_ptr<MenuDefinition> &Def, uint16_t Flags) { - assert(Def); - const MenuDefinition *DefPtr = Def.get(); - - if (auto *MenuItemPtr = dyn_cast<MenuItem>(DefPtr)) { - writeInt<uint16_t>(Flags); - // Some resource files use -1, i.e. UINT32_MAX, for empty menu items. - if (MenuItemPtr->Id != static_cast<uint32_t>(-1)) - RETURN_IF_ERROR( - checkNumberFits<uint16_t>(MenuItemPtr->Id, "MENUITEM action ID")); - writeInt<uint16_t>(MenuItemPtr->Id); - RETURN_IF_ERROR(writeCString(MenuItemPtr->Name)); - return Error::success(); - } - - if (isa<MenuSeparator>(DefPtr)) { - writeInt<uint16_t>(Flags); - writeInt<uint32_t>(0); - return Error::success(); - } - - auto *PopupPtr = cast<PopupItem>(DefPtr); - writeInt<uint16_t>(Flags); - RETURN_IF_ERROR(writeCString(PopupPtr->Name)); - return writeMenuDefinitionList(PopupPtr->SubItems); -} - -Error ResourceFileWriter::writeMenuDefinitionList( - const MenuDefinitionList &List) { - for (auto &Def : List.Definitions) { - uint16_t Flags = Def->getResFlags(); - // Last element receives an additional 0x80 flag. - const uint16_t LastElementFlag = 0x0080; - if (&Def == &List.Definitions.back()) - Flags |= LastElementFlag; - - RETURN_IF_ERROR(writeMenuDefinition(Def, Flags)); - } - return Error::success(); -} - -Error ResourceFileWriter::writeMenuBody(const RCResource *Base) { - // At first, MENUHEADER structure. In fact, these are two WORDs equal to 0. - // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648018.aspx - writeInt<uint32_t>(0); - - return writeMenuDefinitionList(cast<MenuResource>(Base)->Elements); -} - -// --- StringTableResource helpers. --- // - -class BundleResource : public RCResource { -public: - using BundleType = ResourceFileWriter::StringTableInfo::Bundle; - BundleType Bundle; - - BundleResource(const BundleType &StrBundle) - : RCResource(StrBundle.MemoryFlags), Bundle(StrBundle) {} - IntOrString getResourceType() const override { return 6; } - - ResourceKind getKind() const override { return RkStringTableBundle; } - static bool classof(const RCResource *Res) { - return Res->getKind() == RkStringTableBundle; - } - Twine getResourceTypeName() const override { return "STRINGTABLE"; } -}; - -Error ResourceFileWriter::visitStringTableBundle(const RCResource *Res) { - return writeResource(Res, &ResourceFileWriter::writeStringTableBundleBody); -} - -Error ResourceFileWriter::insertStringIntoBundle( - StringTableInfo::Bundle &Bundle, uint16_t StringID, - const std::vector<StringRef> &String) { - uint16_t StringLoc = StringID & 15; - if (Bundle.Data[StringLoc]) - return createError("Multiple STRINGTABLE strings located under ID " + - Twine(StringID)); - Bundle.Data[StringLoc] = String; - return Error::success(); -} - -Error ResourceFileWriter::writeStringTableBundleBody(const RCResource *Base) { - auto *Res = cast<BundleResource>(Base); - for (size_t ID = 0; ID < Res->Bundle.Data.size(); ++ID) { - // The string format is a tiny bit different here. We - // first output the size of the string, and then the string itself - // (which is not null-terminated). - SmallVector<UTF16, 128> Data; - if (Res->Bundle.Data[ID]) { - bool IsLongString; - for (StringRef S : *Res->Bundle.Data[ID]) - RETURN_IF_ERROR(processString(S, NullHandlingMethod::CutAtDoubleNull, - IsLongString, Data, Params.CodePage)); - if (AppendNull) - Data.push_back('\0'); - } - RETURN_IF_ERROR( - checkNumberFits<uint16_t>(Data.size(), "STRINGTABLE string size")); - writeInt<uint16_t>(Data.size()); - for (auto Char : Data) - writeInt(Char); - } - return Error::success(); -} - -Error ResourceFileWriter::dumpAllStringTables() { - for (auto Key : StringTableData.BundleList) { - auto Iter = StringTableData.BundleData.find(Key); - assert(Iter != StringTableData.BundleData.end()); - - // For a moment, revert the context info to moment of bundle declaration. - ContextKeeper RAII(this); - ObjectData = Iter->second.DeclTimeInfo; - - BundleResource Res(Iter->second); - // Bundle #(k+1) contains keys [16k, 16k + 15]. - Res.setName(Key.first + 1); - RETURN_IF_ERROR(visitStringTableBundle(&Res)); - } - return Error::success(); -} - -// --- UserDefinedResource helpers. --- // - -Error ResourceFileWriter::writeUserDefinedBody(const RCResource *Base) { - auto *Res = cast<UserDefinedResource>(Base); - - if (Res->IsFileResource) - return appendFile(Res->FileLoc); - - for (auto &Elem : Res->Contents) { - if (Elem.isInt()) { - RETURN_IF_ERROR( - checkRCInt(Elem.getInt(), "Number in user-defined resource")); - writeRCInt(Elem.getInt()); - continue; - } - - SmallVector<UTF16, 128> ProcessedString; - bool IsLongString; - RETURN_IF_ERROR( - processString(Elem.getString(), NullHandlingMethod::UserResource, - IsLongString, ProcessedString, Params.CodePage)); - - for (auto Ch : ProcessedString) { - if (IsLongString) { - writeInt(Ch); - continue; - } - - RETURN_IF_ERROR(checkNumberFits<uint8_t>( - Ch, "Character in narrow string in user-defined resource")); - writeInt<uint8_t>(Ch); - } - } - - return Error::success(); -} - -// --- VersionInfoResourceResource helpers. --- // - -Error ResourceFileWriter::writeVersionInfoBlock(const VersionInfoBlock &Blk) { - // Output the header if the block has name. - bool OutputHeader = Blk.Name != ""; - uint64_t LengthLoc; - - padStream(sizeof(uint32_t)); - if (OutputHeader) { - LengthLoc = writeInt<uint16_t>(0); - writeInt<uint16_t>(0); - writeInt<uint16_t>(1); // true - RETURN_IF_ERROR(writeCString(Blk.Name)); - padStream(sizeof(uint32_t)); - } - - for (const std::unique_ptr<VersionInfoStmt> &Item : Blk.Stmts) { - VersionInfoStmt *ItemPtr = Item.get(); - - if (auto *BlockPtr = dyn_cast<VersionInfoBlock>(ItemPtr)) { - RETURN_IF_ERROR(writeVersionInfoBlock(*BlockPtr)); - continue; - } - - auto *ValuePtr = cast<VersionInfoValue>(ItemPtr); - RETURN_IF_ERROR(writeVersionInfoValue(*ValuePtr)); - } - - if (OutputHeader) { - uint64_t CurLoc = tell(); - writeObjectAt(ulittle16_t(CurLoc - LengthLoc), LengthLoc); - } - - return Error::success(); -} - -Error ResourceFileWriter::writeVersionInfoValue(const VersionInfoValue &Val) { - // rc has a peculiar algorithm to output VERSIONINFO VALUEs. Each VALUE - // is a mapping from the key (string) to the value (a sequence of ints or - // a sequence of strings). - // - // If integers are to be written: width of each integer written depends on - // whether it's been declared 'long' (it's DWORD then) or not (it's WORD). - // ValueLength defined in structure referenced below is then the total - // number of bytes taken by these integers. - // - // If strings are to be written: characters are always WORDs. - // Moreover, '\0' character is written after the last string, and between - // every two strings separated by comma (if strings are not comma-separated, - // they're simply concatenated). ValueLength is equal to the number of WORDs - // written (that is, half of the bytes written). - // - // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms646994.aspx - bool HasStrings = false, HasInts = false; - for (auto &Item : Val.Values) - (Item.isInt() ? HasInts : HasStrings) = true; - - assert((HasStrings || HasInts) && "VALUE must have at least one argument"); - if (HasStrings && HasInts) - return createError(Twine("VALUE ") + Val.Key + - " cannot contain both strings and integers"); - - padStream(sizeof(uint32_t)); - auto LengthLoc = writeInt<uint16_t>(0); - auto ValLengthLoc = writeInt<uint16_t>(0); - writeInt<uint16_t>(HasStrings); - RETURN_IF_ERROR(writeCString(Val.Key)); - padStream(sizeof(uint32_t)); - - auto DataLoc = tell(); - for (size_t Id = 0; Id < Val.Values.size(); ++Id) { - auto &Item = Val.Values[Id]; - if (Item.isInt()) { - auto Value = Item.getInt(); - RETURN_IF_ERROR(checkRCInt(Value, "VERSIONINFO integer value")); - writeRCInt(Value); - continue; - } - - bool WriteTerminator = - Id == Val.Values.size() - 1 || Val.HasPrecedingComma[Id + 1]; - RETURN_IF_ERROR(writeCString(Item.getString(), WriteTerminator)); - } - - auto CurLoc = tell(); - auto ValueLength = CurLoc - DataLoc; - if (HasStrings) { - assert(ValueLength % 2 == 0); - ValueLength /= 2; - } - writeObjectAt(ulittle16_t(CurLoc - LengthLoc), LengthLoc); - writeObjectAt(ulittle16_t(ValueLength), ValLengthLoc); - return Error::success(); -} - -template <typename Ty> -static Ty getWithDefault(const StringMap<Ty> &Map, StringRef Key, - const Ty &Default) { - auto Iter = Map.find(Key); - if (Iter != Map.end()) - return Iter->getValue(); - return Default; -} - -Error ResourceFileWriter::writeVersionInfoBody(const RCResource *Base) { - auto *Res = cast<VersionInfoResource>(Base); - - const auto &FixedData = Res->FixedData; - - struct /* VS_FIXEDFILEINFO */ { - ulittle32_t Signature = ulittle32_t(0xFEEF04BD); - ulittle32_t StructVersion = ulittle32_t(0x10000); - // It's weird to have most-significant DWORD first on the little-endian - // machines, but let it be this way. - ulittle32_t FileVersionMS; - ulittle32_t FileVersionLS; - ulittle32_t ProductVersionMS; - ulittle32_t ProductVersionLS; - ulittle32_t FileFlagsMask; - ulittle32_t FileFlags; - ulittle32_t FileOS; - ulittle32_t FileType; - ulittle32_t FileSubtype; - // MS implementation seems to always set these fields to 0. - ulittle32_t FileDateMS = ulittle32_t(0); - ulittle32_t FileDateLS = ulittle32_t(0); - } FixedInfo; - - // First, VS_VERSIONINFO. - auto LengthLoc = writeInt<uint16_t>(0); - writeInt<uint16_t>(sizeof(FixedInfo)); - writeInt<uint16_t>(0); - cantFail(writeCString("VS_VERSION_INFO")); - padStream(sizeof(uint32_t)); - - using VersionInfoFixed = VersionInfoResource::VersionInfoFixed; - auto GetField = [&](VersionInfoFixed::VersionInfoFixedType Type) { - static const SmallVector<uint32_t, 4> DefaultOut{0, 0, 0, 0}; - if (!FixedData.IsTypePresent[(int)Type]) - return DefaultOut; - return FixedData.FixedInfo[(int)Type]; - }; - - auto FileVer = GetField(VersionInfoFixed::FtFileVersion); - RETURN_IF_ERROR(checkNumberFits<uint16_t>( - *std::max_element(FileVer.begin(), FileVer.end()), "FILEVERSION fields")); - FixedInfo.FileVersionMS = (FileVer[0] << 16) | FileVer[1]; - FixedInfo.FileVersionLS = (FileVer[2] << 16) | FileVer[3]; - - auto ProdVer = GetField(VersionInfoFixed::FtProductVersion); - RETURN_IF_ERROR(checkNumberFits<uint16_t>( - *std::max_element(ProdVer.begin(), ProdVer.end()), - "PRODUCTVERSION fields")); - FixedInfo.ProductVersionMS = (ProdVer[0] << 16) | ProdVer[1]; - FixedInfo.ProductVersionLS = (ProdVer[2] << 16) | ProdVer[3]; - - FixedInfo.FileFlagsMask = GetField(VersionInfoFixed::FtFileFlagsMask)[0]; - FixedInfo.FileFlags = GetField(VersionInfoFixed::FtFileFlags)[0]; - FixedInfo.FileOS = GetField(VersionInfoFixed::FtFileOS)[0]; - FixedInfo.FileType = GetField(VersionInfoFixed::FtFileType)[0]; - FixedInfo.FileSubtype = GetField(VersionInfoFixed::FtFileSubtype)[0]; - - writeObject(FixedInfo); - padStream(sizeof(uint32_t)); - - RETURN_IF_ERROR(writeVersionInfoBlock(Res->MainBlock)); - - // FIXME: check overflow? - writeObjectAt(ulittle16_t(tell() - LengthLoc), LengthLoc); - - return Error::success(); -} - -Expected<std::unique_ptr<MemoryBuffer>> -ResourceFileWriter::loadFile(StringRef File) const { - SmallString<128> Path; - SmallString<128> Cwd; - std::unique_ptr<MemoryBuffer> Result; - +// * Squash "" to a single ". +// * Replace the escape sequences with their processed version. +// For identifiers, this is no-op. +static Error processString(StringRef Str, NullHandlingMethod NullHandler, + bool &IsLongString, SmallVectorImpl<UTF16> &Result, + int CodePage) { + bool IsString = stripQuotes(Str, IsLongString); + SmallVector<UTF16, 128> Chars; + + // Convert the input bytes according to the chosen codepage. + if (CodePage == CpUtf8) { + convertUTF8ToUTF16String(Str, Chars); + } else if (CodePage == CpWin1252) { + for (char C : Str) + Chars.push_back(cp1252ToUnicode((unsigned char)C)); + } else { + // For other, unknown codepages, only allow plain ASCII input. + for (char C : Str) { + if ((unsigned char)C > 0x7F) + return createError("Non-ASCII 8-bit codepoint (" + Twine(C) + + ") can't be interpreted in the current codepage"); + Chars.push_back((unsigned char)C); + } + } + + if (!IsString) { + // It's an identifier if it's not a string. Make all characters uppercase. + for (UTF16 &Ch : Chars) { + assert(Ch <= 0x7F && "We didn't allow identifiers to be non-ASCII"); + Ch = toupper(Ch); + } + Result.swap(Chars); + return Error::success(); + } + Result.reserve(Chars.size()); + size_t Pos = 0; + + auto AddRes = [&Result, NullHandler, IsLongString](UTF16 Char) -> Error { + if (!IsLongString) { + if (NullHandler == NullHandlingMethod::UserResource) { + // Narrow strings in user-defined resources are *not* output in + // UTF-16 format. + if (Char > 0xFF) + return createError("Non-8-bit codepoint (" + Twine(Char) + + ") can't occur in a user-defined narrow string"); + } + } + + Result.push_back(Char); + return Error::success(); + }; + auto AddEscapedChar = [AddRes, IsLongString, CodePage](UTF16 Char) -> Error { + if (!IsLongString) { + // Escaped chars in narrow strings have to be interpreted according to + // the chosen code page. + if (Char > 0xFF) + return createError("Non-8-bit escaped char (" + Twine(Char) + + ") can't occur in narrow string"); + if (CodePage == CpUtf8) { + if (Char >= 0x80) + return createError("Unable to interpret single byte (" + Twine(Char) + + ") as UTF-8"); + } else if (CodePage == CpWin1252) { + Char = cp1252ToUnicode(Char); + } else { + // Unknown/unsupported codepage, only allow ASCII input. + if (Char > 0x7F) + return createError("Non-ASCII 8-bit codepoint (" + Twine(Char) + + ") can't " + "occur in a non-Unicode string"); + } + } + + return AddRes(Char); + }; + + while (Pos < Chars.size()) { + UTF16 CurChar = Chars[Pos]; + ++Pos; + + // Strip double "". + if (CurChar == '"') { + if (Pos == Chars.size() || Chars[Pos] != '"') + return createError("Expected \"\""); + ++Pos; + RETURN_IF_ERROR(AddRes('"')); + continue; + } + + if (CurChar == '\\') { + UTF16 TypeChar = Chars[Pos]; + ++Pos; + + if (TypeChar == 'x' || TypeChar == 'X') { + // Read a hex number. Max number of characters to read differs between + // narrow and wide strings. + UTF16 ReadInt = 0; + size_t RemainingChars = IsLongString ? 4 : 2; + // We don't want to read non-ASCII hex digits. std:: functions past + // 0xFF invoke UB. + // + // FIXME: actually, Microsoft version probably doesn't check this + // condition and uses their Unicode version of 'isxdigit'. However, + // there are some hex-digit Unicode character outside of ASCII, and + // some of these are actually accepted by rc.exe, the notable example + // being fullwidth forms (U+FF10..U+FF19 etc.) These can be written + // instead of ASCII digits in \x... escape sequence and get accepted. + // However, the resulting hexcodes seem totally unpredictable. + // We think it's infeasible to try to reproduce this behavior, nor to + // put effort in order to detect it. + while (RemainingChars && Pos < Chars.size() && Chars[Pos] < 0x80) { + if (!isxdigit(Chars[Pos])) + break; + char Digit = tolower(Chars[Pos]); + ++Pos; + + ReadInt <<= 4; + if (isdigit(Digit)) + ReadInt |= Digit - '0'; + else + ReadInt |= Digit - 'a' + 10; + + --RemainingChars; + } + + RETURN_IF_ERROR(AddEscapedChar(ReadInt)); + continue; + } + + if (TypeChar >= '0' && TypeChar < '8') { + // Read an octal number. Note that we've already read the first digit. + UTF16 ReadInt = TypeChar - '0'; + size_t RemainingChars = IsLongString ? 6 : 2; + + while (RemainingChars && Pos < Chars.size() && Chars[Pos] >= '0' && + Chars[Pos] < '8') { + ReadInt <<= 3; + ReadInt |= Chars[Pos] - '0'; + --RemainingChars; + ++Pos; + } + + RETURN_IF_ERROR(AddEscapedChar(ReadInt)); + + continue; + } + + switch (TypeChar) { + case 'A': + case 'a': + // Windows '\a' translates into '\b' (Backspace). + RETURN_IF_ERROR(AddRes('\b')); + break; + + case 'n': // Somehow, RC doesn't recognize '\N' and '\R'. + RETURN_IF_ERROR(AddRes('\n')); + break; + + case 'r': + RETURN_IF_ERROR(AddRes('\r')); + break; + + case 'T': + case 't': + RETURN_IF_ERROR(AddRes('\t')); + break; + + case '\\': + RETURN_IF_ERROR(AddRes('\\')); + break; + + case '"': + // RC accepts \" only if another " comes afterwards; then, \"" means + // a single ". + if (Pos == Chars.size() || Chars[Pos] != '"') + return createError("Expected \\\"\""); + ++Pos; + RETURN_IF_ERROR(AddRes('"')); + break; + + default: + // If TypeChar means nothing, \ is should be output to stdout with + // following char. However, rc.exe consumes these characters when + // dealing with wide strings. + if (!IsLongString) { + RETURN_IF_ERROR(AddRes('\\')); + RETURN_IF_ERROR(AddRes(TypeChar)); + } + break; + } + + continue; + } + + // If nothing interesting happens, just output the character. + RETURN_IF_ERROR(AddRes(CurChar)); + } + + switch (NullHandler) { + case NullHandlingMethod::CutAtNull: + for (size_t Pos = 0; Pos < Result.size(); ++Pos) + if (Result[Pos] == '\0') + Result.resize(Pos); + break; + + case NullHandlingMethod::CutAtDoubleNull: + for (size_t Pos = 0; Pos + 1 < Result.size(); ++Pos) + if (Result[Pos] == '\0' && Result[Pos + 1] == '\0') + Result.resize(Pos); + if (Result.size() > 0 && Result.back() == '\0') + Result.pop_back(); + break; + + case NullHandlingMethod::UserResource: + break; + } + + return Error::success(); +} + +uint64_t ResourceFileWriter::writeObject(const ArrayRef<uint8_t> Data) { + uint64_t Result = tell(); + FS->write((const char *)Data.begin(), Data.size()); + return Result; +} + +Error ResourceFileWriter::writeCString(StringRef Str, bool WriteTerminator) { + SmallVector<UTF16, 128> ProcessedString; + bool IsLongString; + RETURN_IF_ERROR(processString(Str, NullHandlingMethod::CutAtNull, + IsLongString, ProcessedString, + Params.CodePage)); + for (auto Ch : ProcessedString) + writeInt<uint16_t>(Ch); + if (WriteTerminator) + writeInt<uint16_t>(0); + return Error::success(); +} + +Error ResourceFileWriter::writeIdentifier(const IntOrString &Ident) { + return writeIntOrString(Ident); +} + +Error ResourceFileWriter::writeIntOrString(const IntOrString &Value) { + if (!Value.isInt()) + return writeCString(Value.getString()); + + writeInt<uint16_t>(0xFFFF); + writeInt<uint16_t>(Value.getInt()); + return Error::success(); +} + +void ResourceFileWriter::writeRCInt(RCInt Value) { + if (Value.isLong()) + writeInt<uint32_t>(Value); + else + writeInt<uint16_t>(Value); +} + +Error ResourceFileWriter::appendFile(StringRef Filename) { + bool IsLong; + stripQuotes(Filename, IsLong); + + auto File = loadFile(Filename); + if (!File) + return File.takeError(); + + *FS << (*File)->getBuffer(); + return Error::success(); +} + +void ResourceFileWriter::padStream(uint64_t Length) { + assert(Length > 0); + uint64_t Location = tell(); + Location %= Length; + uint64_t Pad = (Length - Location) % Length; + for (uint64_t i = 0; i < Pad; ++i) + writeInt<uint8_t>(0); +} + +Error ResourceFileWriter::handleError(Error Err, const RCResource *Res) { + if (Err) + return joinErrors(createError("Error in " + Res->getResourceTypeName() + + " statement (ID " + Twine(Res->ResName) + + "): "), + std::move(Err)); + return Error::success(); +} + +Error ResourceFileWriter::visitNullResource(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeNullBody); +} + +Error ResourceFileWriter::visitAcceleratorsResource(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeAcceleratorsBody); +} + +Error ResourceFileWriter::visitBitmapResource(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeBitmapBody); +} + +Error ResourceFileWriter::visitCursorResource(const RCResource *Res) { + return handleError(visitIconOrCursorResource(Res), Res); +} + +Error ResourceFileWriter::visitDialogResource(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeDialogBody); +} + +Error ResourceFileWriter::visitIconResource(const RCResource *Res) { + return handleError(visitIconOrCursorResource(Res), Res); +} + +Error ResourceFileWriter::visitCaptionStmt(const CaptionStmt *Stmt) { + ObjectData.Caption = Stmt->Value; + return Error::success(); +} + +Error ResourceFileWriter::visitClassStmt(const ClassStmt *Stmt) { + ObjectData.Class = Stmt->Value; + return Error::success(); +} + +Error ResourceFileWriter::visitHTMLResource(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeHTMLBody); +} + +Error ResourceFileWriter::visitMenuResource(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeMenuBody); +} + +Error ResourceFileWriter::visitStringTableResource(const RCResource *Base) { + const auto *Res = cast<StringTableResource>(Base); + + ContextKeeper RAII(this); + RETURN_IF_ERROR(Res->applyStmts(this)); + + for (auto &String : Res->Table) { + RETURN_IF_ERROR(checkNumberFits<uint16_t>(String.first, "String ID")); + uint16_t BundleID = String.first >> 4; + StringTableInfo::BundleKey Key(BundleID, ObjectData.LanguageInfo); + auto &BundleData = StringTableData.BundleData; + auto Iter = BundleData.find(Key); + + if (Iter == BundleData.end()) { + // Need to create a bundle. + StringTableData.BundleList.push_back(Key); + auto EmplaceResult = BundleData.emplace( + Key, StringTableInfo::Bundle(ObjectData, Res->MemoryFlags)); + assert(EmplaceResult.second && "Could not create a bundle"); + Iter = EmplaceResult.first; + } + + RETURN_IF_ERROR( + insertStringIntoBundle(Iter->second, String.first, String.second)); + } + + return Error::success(); +} + +Error ResourceFileWriter::visitUserDefinedResource(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeUserDefinedBody); +} + +Error ResourceFileWriter::visitVersionInfoResource(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeVersionInfoBody); +} + +Error ResourceFileWriter::visitCharacteristicsStmt( + const CharacteristicsStmt *Stmt) { + ObjectData.Characteristics = Stmt->Value; + return Error::success(); +} + +Error ResourceFileWriter::visitExStyleStmt(const ExStyleStmt *Stmt) { + ObjectData.ExStyle = Stmt->Value; + return Error::success(); +} + +Error ResourceFileWriter::visitFontStmt(const FontStmt *Stmt) { + RETURN_IF_ERROR(checkNumberFits<uint16_t>(Stmt->Size, "Font size")); + RETURN_IF_ERROR(checkNumberFits<uint16_t>(Stmt->Weight, "Font weight")); + RETURN_IF_ERROR(checkNumberFits<uint8_t>(Stmt->Charset, "Font charset")); + ObjectInfo::FontInfo Font{Stmt->Size, Stmt->Name, Stmt->Weight, Stmt->Italic, + Stmt->Charset}; + ObjectData.Font.emplace(Font); + return Error::success(); +} + +Error ResourceFileWriter::visitLanguageStmt(const LanguageResource *Stmt) { + RETURN_IF_ERROR(checkNumberFits(Stmt->Lang, 10, "Primary language ID")); + RETURN_IF_ERROR(checkNumberFits(Stmt->SubLang, 6, "Sublanguage ID")); + ObjectData.LanguageInfo = Stmt->Lang | (Stmt->SubLang << 10); + return Error::success(); +} + +Error ResourceFileWriter::visitStyleStmt(const StyleStmt *Stmt) { + ObjectData.Style = Stmt->Value; + return Error::success(); +} + +Error ResourceFileWriter::visitVersionStmt(const VersionStmt *Stmt) { + ObjectData.VersionInfo = Stmt->Value; + return Error::success(); +} + +Error ResourceFileWriter::writeResource( + const RCResource *Res, + Error (ResourceFileWriter::*BodyWriter)(const RCResource *)) { + // We don't know the sizes yet. + object::WinResHeaderPrefix HeaderPrefix{ulittle32_t(0U), ulittle32_t(0U)}; + uint64_t HeaderLoc = writeObject(HeaderPrefix); + + auto ResType = Res->getResourceType(); + RETURN_IF_ERROR(checkIntOrString(ResType, "Resource type")); + RETURN_IF_ERROR(checkIntOrString(Res->ResName, "Resource ID")); + RETURN_IF_ERROR(handleError(writeIdentifier(ResType), Res)); + RETURN_IF_ERROR(handleError(writeIdentifier(Res->ResName), Res)); + + // Apply the resource-local optional statements. + ContextKeeper RAII(this); + RETURN_IF_ERROR(handleError(Res->applyStmts(this), Res)); + + padStream(sizeof(uint32_t)); + object::WinResHeaderSuffix HeaderSuffix{ + ulittle32_t(0), // DataVersion; seems to always be 0 + ulittle16_t(Res->MemoryFlags), ulittle16_t(ObjectData.LanguageInfo), + ulittle32_t(ObjectData.VersionInfo), + ulittle32_t(ObjectData.Characteristics)}; + writeObject(HeaderSuffix); + + uint64_t DataLoc = tell(); + RETURN_IF_ERROR(handleError((this->*BodyWriter)(Res), Res)); + // RETURN_IF_ERROR(handleError(dumpResource(Ctx))); + + // Update the sizes. + HeaderPrefix.DataSize = tell() - DataLoc; + HeaderPrefix.HeaderSize = DataLoc - HeaderLoc; + writeObjectAt(HeaderPrefix, HeaderLoc); + padStream(sizeof(uint32_t)); + + return Error::success(); +} + +// --- NullResource helpers. --- // + +Error ResourceFileWriter::writeNullBody(const RCResource *) { + return Error::success(); +} + +// --- AcceleratorsResource helpers. --- // + +Error ResourceFileWriter::writeSingleAccelerator( + const AcceleratorsResource::Accelerator &Obj, bool IsLastItem) { + using Accelerator = AcceleratorsResource::Accelerator; + using Opt = Accelerator::Options; + + struct AccelTableEntry { + ulittle16_t Flags; + ulittle16_t ANSICode; + ulittle16_t Id; + uint16_t Padding; + } Entry{ulittle16_t(0), ulittle16_t(0), ulittle16_t(0), 0}; + + bool IsASCII = Obj.Flags & Opt::ASCII, IsVirtKey = Obj.Flags & Opt::VIRTKEY; + + // Remove ASCII flags (which doesn't occur in .res files). + Entry.Flags = Obj.Flags & ~Opt::ASCII; + + if (IsLastItem) + Entry.Flags |= 0x80; + + RETURN_IF_ERROR(checkNumberFits<uint16_t>(Obj.Id, "ACCELERATORS entry ID")); + Entry.Id = ulittle16_t(Obj.Id); + + auto createAccError = [&Obj](const char *Msg) { + return createError("Accelerator ID " + Twine(Obj.Id) + ": " + Msg); + }; + + if (IsASCII && IsVirtKey) + return createAccError("Accelerator can't be both ASCII and VIRTKEY"); + + if (!IsVirtKey && (Obj.Flags & (Opt::ALT | Opt::SHIFT | Opt::CONTROL))) + return createAccError("Can only apply ALT, SHIFT or CONTROL to VIRTKEY" + " accelerators"); + + if (Obj.Event.isInt()) { + if (!IsASCII && !IsVirtKey) + return createAccError( + "Accelerator with a numeric event must be either ASCII" + " or VIRTKEY"); + + uint32_t EventVal = Obj.Event.getInt(); + RETURN_IF_ERROR( + checkNumberFits<uint16_t>(EventVal, "Numeric event key ID")); + Entry.ANSICode = ulittle16_t(EventVal); + writeObject(Entry); + return Error::success(); + } + + StringRef Str = Obj.Event.getString(); + bool IsWide; + stripQuotes(Str, IsWide); + + if (Str.size() == 0 || Str.size() > 2) + return createAccError( + "Accelerator string events should have length 1 or 2"); + + if (Str[0] == '^') { + if (Str.size() == 1) + return createAccError("No character following '^' in accelerator event"); + if (IsVirtKey) + return createAccError( + "VIRTKEY accelerator events can't be preceded by '^'"); + + char Ch = Str[1]; + if (Ch >= 'a' && Ch <= 'z') + Entry.ANSICode = ulittle16_t(Ch - 'a' + 1); + else if (Ch >= 'A' && Ch <= 'Z') + Entry.ANSICode = ulittle16_t(Ch - 'A' + 1); + else + return createAccError("Control character accelerator event should be" + " alphabetic"); + + writeObject(Entry); + return Error::success(); + } + + if (Str.size() == 2) + return createAccError("Event string should be one-character, possibly" + " preceded by '^'"); + + uint8_t EventCh = Str[0]; + // The original tool just warns in this situation. We chose to fail. + if (IsVirtKey && !isalnum(EventCh)) + return createAccError("Non-alphanumeric characters cannot describe virtual" + " keys"); + if (EventCh > 0x7F) + return createAccError("Non-ASCII description of accelerator"); + + if (IsVirtKey) + EventCh = toupper(EventCh); + Entry.ANSICode = ulittle16_t(EventCh); + writeObject(Entry); + return Error::success(); +} + +Error ResourceFileWriter::writeAcceleratorsBody(const RCResource *Base) { + auto *Res = cast<AcceleratorsResource>(Base); + size_t AcceleratorId = 0; + for (auto &Acc : Res->Accelerators) { + ++AcceleratorId; + RETURN_IF_ERROR( + writeSingleAccelerator(Acc, AcceleratorId == Res->Accelerators.size())); + } + return Error::success(); +} + +// --- BitmapResource helpers. --- // + +Error ResourceFileWriter::writeBitmapBody(const RCResource *Base) { + StringRef Filename = cast<BitmapResource>(Base)->BitmapLoc; + bool IsLong; + stripQuotes(Filename, IsLong); + + auto File = loadFile(Filename); + if (!File) + return File.takeError(); + + StringRef Buffer = (*File)->getBuffer(); + + // Skip the 14 byte BITMAPFILEHEADER. + constexpr size_t BITMAPFILEHEADER_size = 14; + if (Buffer.size() < BITMAPFILEHEADER_size || Buffer[0] != 'B' || + Buffer[1] != 'M') + return createError("Incorrect bitmap file."); + + *FS << Buffer.substr(BITMAPFILEHEADER_size); + return Error::success(); +} + +// --- CursorResource and IconResource helpers. --- // + +// ICONRESDIR structure. Describes a single icon in resource group. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648016.aspx +struct IconResDir { + uint8_t Width; + uint8_t Height; + uint8_t ColorCount; + uint8_t Reserved; +}; + +// CURSORDIR structure. Describes a single cursor in resource group. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648011(v=vs.85).aspx +struct CursorDir { + ulittle16_t Width; + ulittle16_t Height; +}; + +// RESDIRENTRY structure, stripped from the last item. Stripping made +// for compatibility with RESDIR. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026(v=vs.85).aspx +struct ResourceDirEntryStart { + union { + CursorDir Cursor; // Used in CURSOR resources. + IconResDir Icon; // Used in .ico and .cur files, and ICON resources. + }; + ulittle16_t Planes; // HotspotX (.cur files but not CURSOR resource). + ulittle16_t BitCount; // HotspotY (.cur files but not CURSOR resource). + ulittle32_t Size; + // ulittle32_t ImageOffset; // Offset to image data (ICONDIRENTRY only). + // ulittle16_t IconID; // Resource icon ID (RESDIR only). +}; + +// BITMAPINFOHEADER structure. Describes basic information about the bitmap +// being read. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx +struct BitmapInfoHeader { + ulittle32_t Size; + ulittle32_t Width; + ulittle32_t Height; + ulittle16_t Planes; + ulittle16_t BitCount; + ulittle32_t Compression; + ulittle32_t SizeImage; + ulittle32_t XPelsPerMeter; + ulittle32_t YPelsPerMeter; + ulittle32_t ClrUsed; + ulittle32_t ClrImportant; +}; + +// Group icon directory header. Called ICONDIR in .ico/.cur files and +// NEWHEADER in .res files. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648023(v=vs.85).aspx +struct GroupIconDir { + ulittle16_t Reserved; // Always 0. + ulittle16_t ResType; // 1 for icons, 2 for cursors. + ulittle16_t ResCount; // Number of items. +}; + +enum class IconCursorGroupType { Icon, Cursor }; + +class SingleIconCursorResource : public RCResource { +public: + IconCursorGroupType Type; + const ResourceDirEntryStart &Header; + ArrayRef<uint8_t> Image; + + SingleIconCursorResource(IconCursorGroupType ResourceType, + const ResourceDirEntryStart &HeaderEntry, + ArrayRef<uint8_t> ImageData, uint16_t Flags) + : RCResource(Flags), Type(ResourceType), Header(HeaderEntry), + Image(ImageData) {} + + Twine getResourceTypeName() const override { return "Icon/cursor image"; } + IntOrString getResourceType() const override { + return Type == IconCursorGroupType::Icon ? RkSingleIcon : RkSingleCursor; + } + ResourceKind getKind() const override { return RkSingleCursorOrIconRes; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkSingleCursorOrIconRes; + } +}; + +class IconCursorGroupResource : public RCResource { +public: + IconCursorGroupType Type; + GroupIconDir Header; + std::vector<ResourceDirEntryStart> ItemEntries; + + IconCursorGroupResource(IconCursorGroupType ResourceType, + const GroupIconDir &HeaderData, + std::vector<ResourceDirEntryStart> &&Entries) + : Type(ResourceType), Header(HeaderData), + ItemEntries(std::move(Entries)) {} + + Twine getResourceTypeName() const override { return "Icon/cursor group"; } + IntOrString getResourceType() const override { + return Type == IconCursorGroupType::Icon ? RkIconGroup : RkCursorGroup; + } + ResourceKind getKind() const override { return RkCursorOrIconGroupRes; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkCursorOrIconGroupRes; + } +}; + +Error ResourceFileWriter::writeSingleIconOrCursorBody(const RCResource *Base) { + auto *Res = cast<SingleIconCursorResource>(Base); + if (Res->Type == IconCursorGroupType::Cursor) { + // In case of cursors, two WORDS are appended to the beginning + // of the resource: HotspotX (Planes in RESDIRENTRY), + // and HotspotY (BitCount). + // + // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026.aspx + // (Remarks section). + writeObject(Res->Header.Planes); + writeObject(Res->Header.BitCount); + } + + writeObject(Res->Image); + return Error::success(); +} + +Error ResourceFileWriter::writeIconOrCursorGroupBody(const RCResource *Base) { + auto *Res = cast<IconCursorGroupResource>(Base); + writeObject(Res->Header); + for (auto Item : Res->ItemEntries) { + writeObject(Item); + writeInt(IconCursorID++); + } + return Error::success(); +} + +Error ResourceFileWriter::visitSingleIconOrCursor(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeSingleIconOrCursorBody); +} + +Error ResourceFileWriter::visitIconOrCursorGroup(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeIconOrCursorGroupBody); +} + +Error ResourceFileWriter::visitIconOrCursorResource(const RCResource *Base) { + IconCursorGroupType Type; + StringRef FileStr; + IntOrString ResName = Base->ResName; + + if (auto *IconRes = dyn_cast<IconResource>(Base)) { + FileStr = IconRes->IconLoc; + Type = IconCursorGroupType::Icon; + } else { + auto *CursorRes = dyn_cast<CursorResource>(Base); + FileStr = CursorRes->CursorLoc; + Type = IconCursorGroupType::Cursor; + } + + bool IsLong; + stripQuotes(FileStr, IsLong); + auto File = loadFile(FileStr); + + if (!File) + return File.takeError(); + + BinaryStreamReader Reader((*File)->getBuffer(), support::little); + + // Read the file headers. + // - At the beginning, ICONDIR/NEWHEADER header. + // - Then, a number of RESDIR headers follow. These contain offsets + // to data. + const GroupIconDir *Header; + + RETURN_IF_ERROR(Reader.readObject(Header)); + if (Header->Reserved != 0) + return createError("Incorrect icon/cursor Reserved field; should be 0."); + uint16_t NeededType = Type == IconCursorGroupType::Icon ? 1 : 2; + if (Header->ResType != NeededType) + return createError("Incorrect icon/cursor ResType field; should be " + + Twine(NeededType) + "."); + + uint16_t NumItems = Header->ResCount; + + // Read single ico/cur headers. + std::vector<ResourceDirEntryStart> ItemEntries; + ItemEntries.reserve(NumItems); + std::vector<uint32_t> ItemOffsets(NumItems); + for (size_t ID = 0; ID < NumItems; ++ID) { + const ResourceDirEntryStart *Object; + RETURN_IF_ERROR(Reader.readObject(Object)); + ItemEntries.push_back(*Object); + RETURN_IF_ERROR(Reader.readInteger(ItemOffsets[ID])); + } + + // Now write each icon/cursors one by one. At first, all the contents + // without ICO/CUR header. This is described by SingleIconCursorResource. + for (size_t ID = 0; ID < NumItems; ++ID) { + // Load the fragment of file. + Reader.setOffset(ItemOffsets[ID]); + ArrayRef<uint8_t> Image; + RETURN_IF_ERROR(Reader.readArray(Image, ItemEntries[ID].Size)); + SingleIconCursorResource SingleRes(Type, ItemEntries[ID], Image, + Base->MemoryFlags); + SingleRes.setName(IconCursorID + ID); + RETURN_IF_ERROR(visitSingleIconOrCursor(&SingleRes)); + } + + // Now, write all the headers concatenated into a separate resource. + for (size_t ID = 0; ID < NumItems; ++ID) { + // We need to rewrite the cursor headers, and fetch actual values + // for Planes/BitCount. + const auto &OldHeader = ItemEntries[ID]; + ResourceDirEntryStart NewHeader = OldHeader; + + if (Type == IconCursorGroupType::Cursor) { + NewHeader.Cursor.Width = OldHeader.Icon.Width; + // Each cursor in fact stores two bitmaps, one under another. + // Height provided in cursor definition describes the height of the + // cursor, whereas the value existing in resource definition describes + // the height of the bitmap. Therefore, we need to double this height. + NewHeader.Cursor.Height = OldHeader.Icon.Height * 2; + + // Two WORDs were written at the beginning of the resource (hotspot + // location). This is reflected in Size field. + NewHeader.Size += 2 * sizeof(uint16_t); + } + + // Now, we actually need to read the bitmap header to find + // the number of planes and the number of bits per pixel. + Reader.setOffset(ItemOffsets[ID]); + const BitmapInfoHeader *BMPHeader; + RETURN_IF_ERROR(Reader.readObject(BMPHeader)); + if (BMPHeader->Size == sizeof(BitmapInfoHeader)) { + NewHeader.Planes = BMPHeader->Planes; + NewHeader.BitCount = BMPHeader->BitCount; + } else { + // A PNG .ico file. + // https://blogs.msdn.microsoft.com/oldnewthing/20101022-00/?p=12473 + // "The image must be in 32bpp" + NewHeader.Planes = 1; + NewHeader.BitCount = 32; + } + + ItemEntries[ID] = NewHeader; + } + + IconCursorGroupResource HeaderRes(Type, *Header, std::move(ItemEntries)); + HeaderRes.setName(ResName); + if (Base->MemoryFlags & MfPreload) { + HeaderRes.MemoryFlags |= MfPreload; + HeaderRes.MemoryFlags &= ~MfPure; + } + RETURN_IF_ERROR(visitIconOrCursorGroup(&HeaderRes)); + + return Error::success(); +} + +// --- DialogResource helpers. --- // + +Error ResourceFileWriter::writeSingleDialogControl(const Control &Ctl, + bool IsExtended) { + // Each control should be aligned to DWORD. + padStream(sizeof(uint32_t)); + + auto TypeInfo = Control::SupportedCtls.lookup(Ctl.Type); + IntWithNotMask CtlStyle(TypeInfo.Style); + CtlStyle |= Ctl.Style.getValueOr(RCInt(0)); + uint32_t CtlExtStyle = Ctl.ExtStyle.getValueOr(0); + + // DIALOG(EX) item header prefix. + if (!IsExtended) { + struct { + ulittle32_t Style; + ulittle32_t ExtStyle; + } Prefix{ulittle32_t(CtlStyle.getValue()), ulittle32_t(CtlExtStyle)}; + writeObject(Prefix); + } else { + struct { + ulittle32_t HelpID; + ulittle32_t ExtStyle; + ulittle32_t Style; + } Prefix{ulittle32_t(Ctl.HelpID.getValueOr(0)), ulittle32_t(CtlExtStyle), + ulittle32_t(CtlStyle.getValue())}; + writeObject(Prefix); + } + + // Common fixed-length part. + RETURN_IF_ERROR(checkSignedNumberFits<int16_t>( + Ctl.X, "Dialog control x-coordinate", true)); + RETURN_IF_ERROR(checkSignedNumberFits<int16_t>( + Ctl.Y, "Dialog control y-coordinate", true)); + RETURN_IF_ERROR( + checkSignedNumberFits<int16_t>(Ctl.Width, "Dialog control width", false)); + RETURN_IF_ERROR(checkSignedNumberFits<int16_t>( + Ctl.Height, "Dialog control height", false)); + struct { + ulittle16_t X; + ulittle16_t Y; + ulittle16_t Width; + ulittle16_t Height; + } Middle{ulittle16_t(Ctl.X), ulittle16_t(Ctl.Y), ulittle16_t(Ctl.Width), + ulittle16_t(Ctl.Height)}; + writeObject(Middle); + + // ID; it's 16-bit in DIALOG and 32-bit in DIALOGEX. + if (!IsExtended) { + // It's common to use -1, i.e. UINT32_MAX, for controls one doesn't + // want to refer to later. + if (Ctl.ID != static_cast<uint32_t>(-1)) + RETURN_IF_ERROR(checkNumberFits<uint16_t>( + Ctl.ID, "Control ID in simple DIALOG resource")); + writeInt<uint16_t>(Ctl.ID); + } else { + writeInt<uint32_t>(Ctl.ID); + } + + // Window class - either 0xFFFF + 16-bit integer or a string. + RETURN_IF_ERROR(writeIntOrString(Ctl.Class)); + + // Element caption/reference ID. ID is preceded by 0xFFFF. + RETURN_IF_ERROR(checkIntOrString(Ctl.Title, "Control reference ID")); + RETURN_IF_ERROR(writeIntOrString(Ctl.Title)); + + // # bytes of extra creation data count. Don't pass any. + writeInt<uint16_t>(0); + + return Error::success(); +} + +Error ResourceFileWriter::writeDialogBody(const RCResource *Base) { + auto *Res = cast<DialogResource>(Base); + + // Default style: WS_POPUP | WS_BORDER | WS_SYSMENU. + const uint32_t DefaultStyle = 0x80880000; + const uint32_t StyleFontFlag = 0x40; + const uint32_t StyleCaptionFlag = 0x00C00000; + + uint32_t UsedStyle = ObjectData.Style.getValueOr(DefaultStyle); + if (ObjectData.Font) + UsedStyle |= StyleFontFlag; + else + UsedStyle &= ~StyleFontFlag; + + // Actually, in case of empty (but existent) caption, the examined field + // is equal to "\"\"". That's why empty captions are still noticed. + if (ObjectData.Caption != "") + UsedStyle |= StyleCaptionFlag; + + const uint16_t DialogExMagic = 0xFFFF; + uint32_t ExStyle = ObjectData.ExStyle.getValueOr(0); + + // Write DIALOG(EX) header prefix. These are pretty different. + if (!Res->IsExtended) { + // We cannot let the higher word of DefaultStyle be equal to 0xFFFF. + // In such a case, whole object (in .res file) is equivalent to a + // DIALOGEX. It might lead to access violation/segmentation fault in + // resource readers. For example, + // 1 DIALOG 0, 0, 0, 65432 + // STYLE 0xFFFF0001 {} + // would be compiled to a DIALOGEX with 65432 controls. + if ((UsedStyle >> 16) == DialogExMagic) + return createError("16 higher bits of DIALOG resource style cannot be" + " equal to 0xFFFF"); + + struct { + ulittle32_t Style; + ulittle32_t ExtStyle; + } Prefix{ulittle32_t(UsedStyle), + ulittle32_t(ExStyle)}; + + writeObject(Prefix); + } else { + struct { + ulittle16_t Version; + ulittle16_t Magic; + ulittle32_t HelpID; + ulittle32_t ExtStyle; + ulittle32_t Style; + } Prefix{ulittle16_t(1), ulittle16_t(DialogExMagic), + ulittle32_t(Res->HelpID), ulittle32_t(ExStyle), ulittle32_t(UsedStyle)}; + + writeObject(Prefix); + } + + // Now, a common part. First, fixed-length fields. + RETURN_IF_ERROR(checkNumberFits<uint16_t>(Res->Controls.size(), + "Number of dialog controls")); + RETURN_IF_ERROR( + checkSignedNumberFits<int16_t>(Res->X, "Dialog x-coordinate", true)); + RETURN_IF_ERROR( + checkSignedNumberFits<int16_t>(Res->Y, "Dialog y-coordinate", true)); + RETURN_IF_ERROR( + checkSignedNumberFits<int16_t>(Res->Width, "Dialog width", false)); + RETURN_IF_ERROR( + checkSignedNumberFits<int16_t>(Res->Height, "Dialog height", false)); + struct { + ulittle16_t Count; + ulittle16_t PosX; + ulittle16_t PosY; + ulittle16_t DialogWidth; + ulittle16_t DialogHeight; + } Middle{ulittle16_t(Res->Controls.size()), ulittle16_t(Res->X), + ulittle16_t(Res->Y), ulittle16_t(Res->Width), + ulittle16_t(Res->Height)}; + writeObject(Middle); + + // MENU field. As of now, we don't keep them in the state and can peacefully + // think there is no menu attached to the dialog. + writeInt<uint16_t>(0); + + // Window CLASS field. + RETURN_IF_ERROR(writeIntOrString(ObjectData.Class)); + + // Window title or a single word equal to 0. + RETURN_IF_ERROR(writeCString(ObjectData.Caption)); + + // If there *is* a window font declared, output its data. + auto &Font = ObjectData.Font; + if (Font) { + writeInt<uint16_t>(Font->Size); + // Additional description occurs only in DIALOGEX. + if (Res->IsExtended) { + writeInt<uint16_t>(Font->Weight); + writeInt<uint8_t>(Font->IsItalic); + writeInt<uint8_t>(Font->Charset); + } + RETURN_IF_ERROR(writeCString(Font->Typeface)); + } + + auto handleCtlError = [&](Error &&Err, const Control &Ctl) -> Error { + if (!Err) + return Error::success(); + return joinErrors(createError("Error in " + Twine(Ctl.Type) + + " control (ID " + Twine(Ctl.ID) + "):"), + std::move(Err)); + }; + + for (auto &Ctl : Res->Controls) + RETURN_IF_ERROR( + handleCtlError(writeSingleDialogControl(Ctl, Res->IsExtended), Ctl)); + + return Error::success(); +} + +// --- HTMLResource helpers. --- // + +Error ResourceFileWriter::writeHTMLBody(const RCResource *Base) { + return appendFile(cast<HTMLResource>(Base)->HTMLLoc); +} + +// --- MenuResource helpers. --- // + +Error ResourceFileWriter::writeMenuDefinition( + const std::unique_ptr<MenuDefinition> &Def, uint16_t Flags) { + assert(Def); + const MenuDefinition *DefPtr = Def.get(); + + if (auto *MenuItemPtr = dyn_cast<MenuItem>(DefPtr)) { + writeInt<uint16_t>(Flags); + // Some resource files use -1, i.e. UINT32_MAX, for empty menu items. + if (MenuItemPtr->Id != static_cast<uint32_t>(-1)) + RETURN_IF_ERROR( + checkNumberFits<uint16_t>(MenuItemPtr->Id, "MENUITEM action ID")); + writeInt<uint16_t>(MenuItemPtr->Id); + RETURN_IF_ERROR(writeCString(MenuItemPtr->Name)); + return Error::success(); + } + + if (isa<MenuSeparator>(DefPtr)) { + writeInt<uint16_t>(Flags); + writeInt<uint32_t>(0); + return Error::success(); + } + + auto *PopupPtr = cast<PopupItem>(DefPtr); + writeInt<uint16_t>(Flags); + RETURN_IF_ERROR(writeCString(PopupPtr->Name)); + return writeMenuDefinitionList(PopupPtr->SubItems); +} + +Error ResourceFileWriter::writeMenuDefinitionList( + const MenuDefinitionList &List) { + for (auto &Def : List.Definitions) { + uint16_t Flags = Def->getResFlags(); + // Last element receives an additional 0x80 flag. + const uint16_t LastElementFlag = 0x0080; + if (&Def == &List.Definitions.back()) + Flags |= LastElementFlag; + + RETURN_IF_ERROR(writeMenuDefinition(Def, Flags)); + } + return Error::success(); +} + +Error ResourceFileWriter::writeMenuBody(const RCResource *Base) { + // At first, MENUHEADER structure. In fact, these are two WORDs equal to 0. + // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648018.aspx + writeInt<uint32_t>(0); + + return writeMenuDefinitionList(cast<MenuResource>(Base)->Elements); +} + +// --- StringTableResource helpers. --- // + +class BundleResource : public RCResource { +public: + using BundleType = ResourceFileWriter::StringTableInfo::Bundle; + BundleType Bundle; + + BundleResource(const BundleType &StrBundle) + : RCResource(StrBundle.MemoryFlags), Bundle(StrBundle) {} + IntOrString getResourceType() const override { return 6; } + + ResourceKind getKind() const override { return RkStringTableBundle; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkStringTableBundle; + } + Twine getResourceTypeName() const override { return "STRINGTABLE"; } +}; + +Error ResourceFileWriter::visitStringTableBundle(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeStringTableBundleBody); +} + +Error ResourceFileWriter::insertStringIntoBundle( + StringTableInfo::Bundle &Bundle, uint16_t StringID, + const std::vector<StringRef> &String) { + uint16_t StringLoc = StringID & 15; + if (Bundle.Data[StringLoc]) + return createError("Multiple STRINGTABLE strings located under ID " + + Twine(StringID)); + Bundle.Data[StringLoc] = String; + return Error::success(); +} + +Error ResourceFileWriter::writeStringTableBundleBody(const RCResource *Base) { + auto *Res = cast<BundleResource>(Base); + for (size_t ID = 0; ID < Res->Bundle.Data.size(); ++ID) { + // The string format is a tiny bit different here. We + // first output the size of the string, and then the string itself + // (which is not null-terminated). + SmallVector<UTF16, 128> Data; + if (Res->Bundle.Data[ID]) { + bool IsLongString; + for (StringRef S : *Res->Bundle.Data[ID]) + RETURN_IF_ERROR(processString(S, NullHandlingMethod::CutAtDoubleNull, + IsLongString, Data, Params.CodePage)); + if (AppendNull) + Data.push_back('\0'); + } + RETURN_IF_ERROR( + checkNumberFits<uint16_t>(Data.size(), "STRINGTABLE string size")); + writeInt<uint16_t>(Data.size()); + for (auto Char : Data) + writeInt(Char); + } + return Error::success(); +} + +Error ResourceFileWriter::dumpAllStringTables() { + for (auto Key : StringTableData.BundleList) { + auto Iter = StringTableData.BundleData.find(Key); + assert(Iter != StringTableData.BundleData.end()); + + // For a moment, revert the context info to moment of bundle declaration. + ContextKeeper RAII(this); + ObjectData = Iter->second.DeclTimeInfo; + + BundleResource Res(Iter->second); + // Bundle #(k+1) contains keys [16k, 16k + 15]. + Res.setName(Key.first + 1); + RETURN_IF_ERROR(visitStringTableBundle(&Res)); + } + return Error::success(); +} + +// --- UserDefinedResource helpers. --- // + +Error ResourceFileWriter::writeUserDefinedBody(const RCResource *Base) { + auto *Res = cast<UserDefinedResource>(Base); + + if (Res->IsFileResource) + return appendFile(Res->FileLoc); + + for (auto &Elem : Res->Contents) { + if (Elem.isInt()) { + RETURN_IF_ERROR( + checkRCInt(Elem.getInt(), "Number in user-defined resource")); + writeRCInt(Elem.getInt()); + continue; + } + + SmallVector<UTF16, 128> ProcessedString; + bool IsLongString; + RETURN_IF_ERROR( + processString(Elem.getString(), NullHandlingMethod::UserResource, + IsLongString, ProcessedString, Params.CodePage)); + + for (auto Ch : ProcessedString) { + if (IsLongString) { + writeInt(Ch); + continue; + } + + RETURN_IF_ERROR(checkNumberFits<uint8_t>( + Ch, "Character in narrow string in user-defined resource")); + writeInt<uint8_t>(Ch); + } + } + + return Error::success(); +} + +// --- VersionInfoResourceResource helpers. --- // + +Error ResourceFileWriter::writeVersionInfoBlock(const VersionInfoBlock &Blk) { + // Output the header if the block has name. + bool OutputHeader = Blk.Name != ""; + uint64_t LengthLoc; + + padStream(sizeof(uint32_t)); + if (OutputHeader) { + LengthLoc = writeInt<uint16_t>(0); + writeInt<uint16_t>(0); + writeInt<uint16_t>(1); // true + RETURN_IF_ERROR(writeCString(Blk.Name)); + padStream(sizeof(uint32_t)); + } + + for (const std::unique_ptr<VersionInfoStmt> &Item : Blk.Stmts) { + VersionInfoStmt *ItemPtr = Item.get(); + + if (auto *BlockPtr = dyn_cast<VersionInfoBlock>(ItemPtr)) { + RETURN_IF_ERROR(writeVersionInfoBlock(*BlockPtr)); + continue; + } + + auto *ValuePtr = cast<VersionInfoValue>(ItemPtr); + RETURN_IF_ERROR(writeVersionInfoValue(*ValuePtr)); + } + + if (OutputHeader) { + uint64_t CurLoc = tell(); + writeObjectAt(ulittle16_t(CurLoc - LengthLoc), LengthLoc); + } + + return Error::success(); +} + +Error ResourceFileWriter::writeVersionInfoValue(const VersionInfoValue &Val) { + // rc has a peculiar algorithm to output VERSIONINFO VALUEs. Each VALUE + // is a mapping from the key (string) to the value (a sequence of ints or + // a sequence of strings). + // + // If integers are to be written: width of each integer written depends on + // whether it's been declared 'long' (it's DWORD then) or not (it's WORD). + // ValueLength defined in structure referenced below is then the total + // number of bytes taken by these integers. + // + // If strings are to be written: characters are always WORDs. + // Moreover, '\0' character is written after the last string, and between + // every two strings separated by comma (if strings are not comma-separated, + // they're simply concatenated). ValueLength is equal to the number of WORDs + // written (that is, half of the bytes written). + // + // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms646994.aspx + bool HasStrings = false, HasInts = false; + for (auto &Item : Val.Values) + (Item.isInt() ? HasInts : HasStrings) = true; + + assert((HasStrings || HasInts) && "VALUE must have at least one argument"); + if (HasStrings && HasInts) + return createError(Twine("VALUE ") + Val.Key + + " cannot contain both strings and integers"); + + padStream(sizeof(uint32_t)); + auto LengthLoc = writeInt<uint16_t>(0); + auto ValLengthLoc = writeInt<uint16_t>(0); + writeInt<uint16_t>(HasStrings); + RETURN_IF_ERROR(writeCString(Val.Key)); + padStream(sizeof(uint32_t)); + + auto DataLoc = tell(); + for (size_t Id = 0; Id < Val.Values.size(); ++Id) { + auto &Item = Val.Values[Id]; + if (Item.isInt()) { + auto Value = Item.getInt(); + RETURN_IF_ERROR(checkRCInt(Value, "VERSIONINFO integer value")); + writeRCInt(Value); + continue; + } + + bool WriteTerminator = + Id == Val.Values.size() - 1 || Val.HasPrecedingComma[Id + 1]; + RETURN_IF_ERROR(writeCString(Item.getString(), WriteTerminator)); + } + + auto CurLoc = tell(); + auto ValueLength = CurLoc - DataLoc; + if (HasStrings) { + assert(ValueLength % 2 == 0); + ValueLength /= 2; + } + writeObjectAt(ulittle16_t(CurLoc - LengthLoc), LengthLoc); + writeObjectAt(ulittle16_t(ValueLength), ValLengthLoc); + return Error::success(); +} + +template <typename Ty> +static Ty getWithDefault(const StringMap<Ty> &Map, StringRef Key, + const Ty &Default) { + auto Iter = Map.find(Key); + if (Iter != Map.end()) + return Iter->getValue(); + return Default; +} + +Error ResourceFileWriter::writeVersionInfoBody(const RCResource *Base) { + auto *Res = cast<VersionInfoResource>(Base); + + const auto &FixedData = Res->FixedData; + + struct /* VS_FIXEDFILEINFO */ { + ulittle32_t Signature = ulittle32_t(0xFEEF04BD); + ulittle32_t StructVersion = ulittle32_t(0x10000); + // It's weird to have most-significant DWORD first on the little-endian + // machines, but let it be this way. + ulittle32_t FileVersionMS; + ulittle32_t FileVersionLS; + ulittle32_t ProductVersionMS; + ulittle32_t ProductVersionLS; + ulittle32_t FileFlagsMask; + ulittle32_t FileFlags; + ulittle32_t FileOS; + ulittle32_t FileType; + ulittle32_t FileSubtype; + // MS implementation seems to always set these fields to 0. + ulittle32_t FileDateMS = ulittle32_t(0); + ulittle32_t FileDateLS = ulittle32_t(0); + } FixedInfo; + + // First, VS_VERSIONINFO. + auto LengthLoc = writeInt<uint16_t>(0); + writeInt<uint16_t>(sizeof(FixedInfo)); + writeInt<uint16_t>(0); + cantFail(writeCString("VS_VERSION_INFO")); + padStream(sizeof(uint32_t)); + + using VersionInfoFixed = VersionInfoResource::VersionInfoFixed; + auto GetField = [&](VersionInfoFixed::VersionInfoFixedType Type) { + static const SmallVector<uint32_t, 4> DefaultOut{0, 0, 0, 0}; + if (!FixedData.IsTypePresent[(int)Type]) + return DefaultOut; + return FixedData.FixedInfo[(int)Type]; + }; + + auto FileVer = GetField(VersionInfoFixed::FtFileVersion); + RETURN_IF_ERROR(checkNumberFits<uint16_t>( + *std::max_element(FileVer.begin(), FileVer.end()), "FILEVERSION fields")); + FixedInfo.FileVersionMS = (FileVer[0] << 16) | FileVer[1]; + FixedInfo.FileVersionLS = (FileVer[2] << 16) | FileVer[3]; + + auto ProdVer = GetField(VersionInfoFixed::FtProductVersion); + RETURN_IF_ERROR(checkNumberFits<uint16_t>( + *std::max_element(ProdVer.begin(), ProdVer.end()), + "PRODUCTVERSION fields")); + FixedInfo.ProductVersionMS = (ProdVer[0] << 16) | ProdVer[1]; + FixedInfo.ProductVersionLS = (ProdVer[2] << 16) | ProdVer[3]; + + FixedInfo.FileFlagsMask = GetField(VersionInfoFixed::FtFileFlagsMask)[0]; + FixedInfo.FileFlags = GetField(VersionInfoFixed::FtFileFlags)[0]; + FixedInfo.FileOS = GetField(VersionInfoFixed::FtFileOS)[0]; + FixedInfo.FileType = GetField(VersionInfoFixed::FtFileType)[0]; + FixedInfo.FileSubtype = GetField(VersionInfoFixed::FtFileSubtype)[0]; + + writeObject(FixedInfo); + padStream(sizeof(uint32_t)); + + RETURN_IF_ERROR(writeVersionInfoBlock(Res->MainBlock)); + + // FIXME: check overflow? + writeObjectAt(ulittle16_t(tell() - LengthLoc), LengthLoc); + + return Error::success(); +} + +Expected<std::unique_ptr<MemoryBuffer>> +ResourceFileWriter::loadFile(StringRef File) const { + SmallString<128> Path; + SmallString<128> Cwd; + std::unique_ptr<MemoryBuffer> Result; + // 0. The file path is absolute or has a root directory, so we shouldn't // try to append it on top of other base directories. (An absolute path // must have a root directory, but e.g. the path "\dir\file" on windows @@ -1524,38 +1524,38 @@ ResourceFileWriter::loadFile(StringRef File) const { // properly though, so if using that to append paths below, this early // exception case could be removed.) if (sys::path::has_root_directory(File)) - return errorOrToExpected(MemoryBuffer::getFile(File, -1, false)); - - // 1. The current working directory. - sys::fs::current_path(Cwd); - Path.assign(Cwd.begin(), Cwd.end()); - sys::path::append(Path, File); - if (sys::fs::exists(Path)) - return errorOrToExpected(MemoryBuffer::getFile(Path, -1, false)); - - // 2. The directory of the input resource file, if it is different from the - // current working directory. - StringRef InputFileDir = sys::path::parent_path(Params.InputFilePath); - Path.assign(InputFileDir.begin(), InputFileDir.end()); - sys::path::append(Path, File); - if (sys::fs::exists(Path)) - return errorOrToExpected(MemoryBuffer::getFile(Path, -1, false)); - - // 3. All of the include directories specified on the command line. - for (StringRef ForceInclude : Params.Include) { - Path.assign(ForceInclude.begin(), ForceInclude.end()); - sys::path::append(Path, File); - if (sys::fs::exists(Path)) - return errorOrToExpected(MemoryBuffer::getFile(Path, -1, false)); - } - - if (auto Result = - llvm::sys::Process::FindInEnvPath("INCLUDE", File, Params.NoInclude)) - return errorOrToExpected(MemoryBuffer::getFile(*Result, -1, false)); - - return make_error<StringError>("error : file not found : " + Twine(File), - inconvertibleErrorCode()); -} - -} // namespace rc -} // namespace llvm + return errorOrToExpected(MemoryBuffer::getFile(File, -1, false)); + + // 1. The current working directory. + sys::fs::current_path(Cwd); + Path.assign(Cwd.begin(), Cwd.end()); + sys::path::append(Path, File); + if (sys::fs::exists(Path)) + return errorOrToExpected(MemoryBuffer::getFile(Path, -1, false)); + + // 2. The directory of the input resource file, if it is different from the + // current working directory. + StringRef InputFileDir = sys::path::parent_path(Params.InputFilePath); + Path.assign(InputFileDir.begin(), InputFileDir.end()); + sys::path::append(Path, File); + if (sys::fs::exists(Path)) + return errorOrToExpected(MemoryBuffer::getFile(Path, -1, false)); + + // 3. All of the include directories specified on the command line. + for (StringRef ForceInclude : Params.Include) { + Path.assign(ForceInclude.begin(), ForceInclude.end()); + sys::path::append(Path, File); + if (sys::fs::exists(Path)) + return errorOrToExpected(MemoryBuffer::getFile(Path, -1, false)); + } + + if (auto Result = + llvm::sys::Process::FindInEnvPath("INCLUDE", File, Params.NoInclude)) + return errorOrToExpected(MemoryBuffer::getFile(*Result, -1, false)); + + return make_error<StringError>("error : file not found : " + Twine(File), + inconvertibleErrorCode()); +} + +} // namespace rc +} // namespace llvm diff --git a/contrib/libs/llvm12/tools/llvm-rc/ResourceFileWriter.h b/contrib/libs/llvm12/tools/llvm-rc/ResourceFileWriter.h index a8802d2220..d545a7a9ca 100644 --- a/contrib/libs/llvm12/tools/llvm-rc/ResourceFileWriter.h +++ b/contrib/libs/llvm12/tools/llvm-rc/ResourceFileWriter.h @@ -1,217 +1,217 @@ -//===-- ResourceSerializator.h ----------------------------------*- C++-*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===---------------------------------------------------------------------===// -// -// This defines a visitor serializing resources to a .res stream. -// -//===---------------------------------------------------------------------===// - -#ifndef LLVM_TOOLS_LLVMRC_RESOURCESERIALIZATOR_H -#define LLVM_TOOLS_LLVMRC_RESOURCESERIALIZATOR_H - -#include "ResourceScriptStmt.h" -#include "ResourceVisitor.h" - -#include "llvm/Support/Endian.h" - -namespace llvm { - -class MemoryBuffer; - -namespace rc { - -enum CodePage { - CpAcp = 0, // The current used codepage. Since there's no such - // notion in LLVM what codepage it actually means, - // this only allows ASCII. - CpWin1252 = 1252, // A codepage where most 8 bit values correspond to - // unicode code points with the same value. - CpUtf8 = 65001, // UTF-8. -}; - -struct WriterParams { - std::vector<std::string> Include; // Additional folders to search for files. - std::vector<std::string> NoInclude; // Folders to exclude from file search. - StringRef InputFilePath; // The full path of the input file. - int CodePage = CpAcp; // The codepage for interpreting characters. -}; - -class ResourceFileWriter : public Visitor { -public: - ResourceFileWriter(const WriterParams &Params, - std::unique_ptr<raw_fd_ostream> Stream) - : Params(Params), FS(std::move(Stream)), IconCursorID(1) { - assert(FS && "Output stream needs to be provided to the serializator"); - } - - Error visitNullResource(const RCResource *) override; - Error visitAcceleratorsResource(const RCResource *) override; - Error visitCursorResource(const RCResource *) override; - Error visitDialogResource(const RCResource *) override; - Error visitHTMLResource(const RCResource *) override; - Error visitIconResource(const RCResource *) override; - Error visitMenuResource(const RCResource *) override; - Error visitVersionInfoResource(const RCResource *) override; - Error visitStringTableResource(const RCResource *) override; - Error visitUserDefinedResource(const RCResource *) override; - - Error visitCaptionStmt(const CaptionStmt *) override; - Error visitCharacteristicsStmt(const CharacteristicsStmt *) override; - Error visitClassStmt(const ClassStmt *) override; - Error visitExStyleStmt(const ExStyleStmt *) override; - Error visitFontStmt(const FontStmt *) override; - Error visitLanguageStmt(const LanguageResource *) override; - Error visitStyleStmt(const StyleStmt *) override; - Error visitVersionStmt(const VersionStmt *) override; - - // Stringtables are output at the end of .res file. We need a separate - // function to do it. - Error dumpAllStringTables(); - - bool AppendNull = false; // Append '\0' to each existing STRINGTABLE element? - - struct ObjectInfo { - uint16_t LanguageInfo; - uint32_t Characteristics; - uint32_t VersionInfo; - - Optional<uint32_t> Style; - Optional<uint32_t> ExStyle; - StringRef Caption; - struct FontInfo { - uint32_t Size; - StringRef Typeface; - uint32_t Weight; - bool IsItalic; - uint32_t Charset; - }; - Optional<FontInfo> Font; - IntOrString Class; - - ObjectInfo() - : LanguageInfo(0), Characteristics(0), VersionInfo(0), - Class(StringRef()) {} - } ObjectData; - - struct StringTableInfo { - // Each STRINGTABLE bundle depends on ID of the bundle and language - // description. - using BundleKey = std::pair<uint16_t, uint16_t>; - // Each bundle is in fact an array of 16 strings. - struct Bundle { - std::array<Optional<std::vector<StringRef>>, 16> Data; - ObjectInfo DeclTimeInfo; - uint16_t MemoryFlags; - Bundle(const ObjectInfo &Info, uint16_t Flags) - : DeclTimeInfo(Info), MemoryFlags(Flags) {} - }; - std::map<BundleKey, Bundle> BundleData; - // Bundles are listed in the order of their first occurrence. - std::vector<BundleKey> BundleList; - } StringTableData; - -private: - Error handleError(Error Err, const RCResource *Res); - - Error - writeResource(const RCResource *Res, - Error (ResourceFileWriter::*BodyWriter)(const RCResource *)); - - // NullResource - Error writeNullBody(const RCResource *); - - // AcceleratorsResource - Error writeSingleAccelerator(const AcceleratorsResource::Accelerator &, - bool IsLastItem); - Error writeAcceleratorsBody(const RCResource *); - - // BitmapResource - Error visitBitmapResource(const RCResource *) override; - Error writeBitmapBody(const RCResource *); - - // CursorResource and IconResource - Error visitIconOrCursorResource(const RCResource *); - Error visitIconOrCursorGroup(const RCResource *); - Error visitSingleIconOrCursor(const RCResource *); - Error writeSingleIconOrCursorBody(const RCResource *); - Error writeIconOrCursorGroupBody(const RCResource *); - - // DialogResource - Error writeSingleDialogControl(const Control &, bool IsExtended); - Error writeDialogBody(const RCResource *); - - // HTMLResource - Error writeHTMLBody(const RCResource *); - - // MenuResource - Error writeMenuDefinition(const std::unique_ptr<MenuDefinition> &, - uint16_t Flags); - Error writeMenuDefinitionList(const MenuDefinitionList &List); - Error writeMenuBody(const RCResource *); - - // StringTableResource - Error visitStringTableBundle(const RCResource *); - Error writeStringTableBundleBody(const RCResource *); - Error insertStringIntoBundle(StringTableInfo::Bundle &Bundle, - uint16_t StringID, - const std::vector<StringRef> &String); - - // User defined resource - Error writeUserDefinedBody(const RCResource *); - - // VersionInfoResource - Error writeVersionInfoBody(const RCResource *); - Error writeVersionInfoBlock(const VersionInfoBlock &); - Error writeVersionInfoValue(const VersionInfoValue &); - - const WriterParams &Params; - - // Output stream handling. - std::unique_ptr<raw_fd_ostream> FS; - - uint64_t tell() const { return FS->tell(); } - - uint64_t writeObject(const ArrayRef<uint8_t> Data); - - template <typename T> uint64_t writeInt(const T &Value) { - support::detail::packed_endian_specific_integral<T, support::little, - support::unaligned> - Object(Value); - return writeObject(Object); - } - - template <typename T> uint64_t writeObject(const T &Value) { - return writeObject(ArrayRef<uint8_t>( - reinterpret_cast<const uint8_t *>(&Value), sizeof(T))); - } - - template <typename T> void writeObjectAt(const T &Value, uint64_t Position) { - FS->pwrite((const char *)&Value, sizeof(T), Position); - } - - Error writeCString(StringRef Str, bool WriteTerminator = true); - - Error writeIdentifier(const IntOrString &Ident); - Error writeIntOrString(const IntOrString &Data); - - void writeRCInt(RCInt); - - Error appendFile(StringRef Filename); - - void padStream(uint64_t Length); - - Expected<std::unique_ptr<MemoryBuffer>> loadFile(StringRef File) const; - - // Icon and cursor IDs are allocated starting from 1 and increasing for - // each icon/cursor dumped. This maintains the current ID to be allocated. - uint16_t IconCursorID; -}; - -} // namespace rc -} // namespace llvm - -#endif +//===-- ResourceSerializator.h ----------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// +// +// This defines a visitor serializing resources to a .res stream. +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_TOOLS_LLVMRC_RESOURCESERIALIZATOR_H +#define LLVM_TOOLS_LLVMRC_RESOURCESERIALIZATOR_H + +#include "ResourceScriptStmt.h" +#include "ResourceVisitor.h" + +#include "llvm/Support/Endian.h" + +namespace llvm { + +class MemoryBuffer; + +namespace rc { + +enum CodePage { + CpAcp = 0, // The current used codepage. Since there's no such + // notion in LLVM what codepage it actually means, + // this only allows ASCII. + CpWin1252 = 1252, // A codepage where most 8 bit values correspond to + // unicode code points with the same value. + CpUtf8 = 65001, // UTF-8. +}; + +struct WriterParams { + std::vector<std::string> Include; // Additional folders to search for files. + std::vector<std::string> NoInclude; // Folders to exclude from file search. + StringRef InputFilePath; // The full path of the input file. + int CodePage = CpAcp; // The codepage for interpreting characters. +}; + +class ResourceFileWriter : public Visitor { +public: + ResourceFileWriter(const WriterParams &Params, + std::unique_ptr<raw_fd_ostream> Stream) + : Params(Params), FS(std::move(Stream)), IconCursorID(1) { + assert(FS && "Output stream needs to be provided to the serializator"); + } + + Error visitNullResource(const RCResource *) override; + Error visitAcceleratorsResource(const RCResource *) override; + Error visitCursorResource(const RCResource *) override; + Error visitDialogResource(const RCResource *) override; + Error visitHTMLResource(const RCResource *) override; + Error visitIconResource(const RCResource *) override; + Error visitMenuResource(const RCResource *) override; + Error visitVersionInfoResource(const RCResource *) override; + Error visitStringTableResource(const RCResource *) override; + Error visitUserDefinedResource(const RCResource *) override; + + Error visitCaptionStmt(const CaptionStmt *) override; + Error visitCharacteristicsStmt(const CharacteristicsStmt *) override; + Error visitClassStmt(const ClassStmt *) override; + Error visitExStyleStmt(const ExStyleStmt *) override; + Error visitFontStmt(const FontStmt *) override; + Error visitLanguageStmt(const LanguageResource *) override; + Error visitStyleStmt(const StyleStmt *) override; + Error visitVersionStmt(const VersionStmt *) override; + + // Stringtables are output at the end of .res file. We need a separate + // function to do it. + Error dumpAllStringTables(); + + bool AppendNull = false; // Append '\0' to each existing STRINGTABLE element? + + struct ObjectInfo { + uint16_t LanguageInfo; + uint32_t Characteristics; + uint32_t VersionInfo; + + Optional<uint32_t> Style; + Optional<uint32_t> ExStyle; + StringRef Caption; + struct FontInfo { + uint32_t Size; + StringRef Typeface; + uint32_t Weight; + bool IsItalic; + uint32_t Charset; + }; + Optional<FontInfo> Font; + IntOrString Class; + + ObjectInfo() + : LanguageInfo(0), Characteristics(0), VersionInfo(0), + Class(StringRef()) {} + } ObjectData; + + struct StringTableInfo { + // Each STRINGTABLE bundle depends on ID of the bundle and language + // description. + using BundleKey = std::pair<uint16_t, uint16_t>; + // Each bundle is in fact an array of 16 strings. + struct Bundle { + std::array<Optional<std::vector<StringRef>>, 16> Data; + ObjectInfo DeclTimeInfo; + uint16_t MemoryFlags; + Bundle(const ObjectInfo &Info, uint16_t Flags) + : DeclTimeInfo(Info), MemoryFlags(Flags) {} + }; + std::map<BundleKey, Bundle> BundleData; + // Bundles are listed in the order of their first occurrence. + std::vector<BundleKey> BundleList; + } StringTableData; + +private: + Error handleError(Error Err, const RCResource *Res); + + Error + writeResource(const RCResource *Res, + Error (ResourceFileWriter::*BodyWriter)(const RCResource *)); + + // NullResource + Error writeNullBody(const RCResource *); + + // AcceleratorsResource + Error writeSingleAccelerator(const AcceleratorsResource::Accelerator &, + bool IsLastItem); + Error writeAcceleratorsBody(const RCResource *); + + // BitmapResource + Error visitBitmapResource(const RCResource *) override; + Error writeBitmapBody(const RCResource *); + + // CursorResource and IconResource + Error visitIconOrCursorResource(const RCResource *); + Error visitIconOrCursorGroup(const RCResource *); + Error visitSingleIconOrCursor(const RCResource *); + Error writeSingleIconOrCursorBody(const RCResource *); + Error writeIconOrCursorGroupBody(const RCResource *); + + // DialogResource + Error writeSingleDialogControl(const Control &, bool IsExtended); + Error writeDialogBody(const RCResource *); + + // HTMLResource + Error writeHTMLBody(const RCResource *); + + // MenuResource + Error writeMenuDefinition(const std::unique_ptr<MenuDefinition> &, + uint16_t Flags); + Error writeMenuDefinitionList(const MenuDefinitionList &List); + Error writeMenuBody(const RCResource *); + + // StringTableResource + Error visitStringTableBundle(const RCResource *); + Error writeStringTableBundleBody(const RCResource *); + Error insertStringIntoBundle(StringTableInfo::Bundle &Bundle, + uint16_t StringID, + const std::vector<StringRef> &String); + + // User defined resource + Error writeUserDefinedBody(const RCResource *); + + // VersionInfoResource + Error writeVersionInfoBody(const RCResource *); + Error writeVersionInfoBlock(const VersionInfoBlock &); + Error writeVersionInfoValue(const VersionInfoValue &); + + const WriterParams &Params; + + // Output stream handling. + std::unique_ptr<raw_fd_ostream> FS; + + uint64_t tell() const { return FS->tell(); } + + uint64_t writeObject(const ArrayRef<uint8_t> Data); + + template <typename T> uint64_t writeInt(const T &Value) { + support::detail::packed_endian_specific_integral<T, support::little, + support::unaligned> + Object(Value); + return writeObject(Object); + } + + template <typename T> uint64_t writeObject(const T &Value) { + return writeObject(ArrayRef<uint8_t>( + reinterpret_cast<const uint8_t *>(&Value), sizeof(T))); + } + + template <typename T> void writeObjectAt(const T &Value, uint64_t Position) { + FS->pwrite((const char *)&Value, sizeof(T), Position); + } + + Error writeCString(StringRef Str, bool WriteTerminator = true); + + Error writeIdentifier(const IntOrString &Ident); + Error writeIntOrString(const IntOrString &Data); + + void writeRCInt(RCInt); + + Error appendFile(StringRef Filename); + + void padStream(uint64_t Length); + + Expected<std::unique_ptr<MemoryBuffer>> loadFile(StringRef File) const; + + // Icon and cursor IDs are allocated starting from 1 and increasing for + // each icon/cursor dumped. This maintains the current ID to be allocated. + uint16_t IconCursorID; +}; + +} // namespace rc +} // namespace llvm + +#endif diff --git a/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptCppFilter.cpp b/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptCppFilter.cpp index 826b212db9..e610be99df 100644 --- a/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptCppFilter.cpp +++ b/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptCppFilter.cpp @@ -1,111 +1,111 @@ -//===-- ResourceScriptCppFilter.cpp ----------------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===---------------------------------------------------------------------===// -// -// This file implements an interface defined in ResourceScriptCppFilter.h. -// -//===---------------------------------------------------------------------===// - -#include "ResourceScriptCppFilter.h" -#include "llvm/ADT/StringExtras.h" - -#include <vector> - -using namespace llvm; - -namespace { - -class Filter { -public: - explicit Filter(StringRef Input) : Data(Input), DataLength(Input.size()) {} - - std::string run(); - -private: - // Parse the line, returning whether the line should be included in - // the output. - bool parseLine(StringRef Line); - - bool streamEof() const; - - StringRef Data; - size_t DataLength; - - size_t Pos = 0; - bool Outputting = true; -}; - -std::string Filter::run() { - std::vector<StringRef> Output; - - while (!streamEof() && Pos != StringRef::npos) { - size_t LineStart = Pos; - Pos = Data.find_first_of("\r\n", Pos); - Pos = Data.find_first_not_of("\r\n", Pos); - StringRef Line = Data.take_front(Pos).drop_front(LineStart); - - if (parseLine(Line)) - Output.push_back(Line); - } - - return llvm::join(Output, ""); -} - -bool Filter::parseLine(StringRef Line) { - Line = Line.ltrim(); - - if (!Line.consume_front("#")) { - // A normal content line, filtered according to the current mode. - return Outputting; - } - - // Found a preprocessing directive line. From here on, we always return - // false since the preprocessing directives should be filtered out. - - Line.consume_front("line"); - if (!Line.startswith(" ")) - return false; // Not a line directive (pragma etc). - - // #line 123 "path/file.h" - // # 123 "path/file.h" 1 - - Line = - Line.ltrim(); // There could be multiple spaces after the #line directive - - size_t N; - if (Line.consumeInteger(10, N)) // Returns true to signify an error - return false; - - Line = Line.ltrim(); - - if (!Line.consume_front("\"")) - return false; // Malformed line, no quote found. - - // Split the string at the last quote (in case the path name had - // escaped quotes as well). - Line = Line.rsplit('"').first; - - StringRef Ext = Line.rsplit('.').second; - - if (Ext.equals_lower("h") || Ext.equals_lower("c")) { - Outputting = false; - } else { - Outputting = true; - } - - return false; -} - -bool Filter::streamEof() const { return Pos == DataLength; } - -} // anonymous namespace - -namespace llvm { - -std::string filterCppOutput(StringRef Input) { return Filter(Input).run(); } - -} // namespace llvm +//===-- ResourceScriptCppFilter.cpp ----------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// +// +// This file implements an interface defined in ResourceScriptCppFilter.h. +// +//===---------------------------------------------------------------------===// + +#include "ResourceScriptCppFilter.h" +#include "llvm/ADT/StringExtras.h" + +#include <vector> + +using namespace llvm; + +namespace { + +class Filter { +public: + explicit Filter(StringRef Input) : Data(Input), DataLength(Input.size()) {} + + std::string run(); + +private: + // Parse the line, returning whether the line should be included in + // the output. + bool parseLine(StringRef Line); + + bool streamEof() const; + + StringRef Data; + size_t DataLength; + + size_t Pos = 0; + bool Outputting = true; +}; + +std::string Filter::run() { + std::vector<StringRef> Output; + + while (!streamEof() && Pos != StringRef::npos) { + size_t LineStart = Pos; + Pos = Data.find_first_of("\r\n", Pos); + Pos = Data.find_first_not_of("\r\n", Pos); + StringRef Line = Data.take_front(Pos).drop_front(LineStart); + + if (parseLine(Line)) + Output.push_back(Line); + } + + return llvm::join(Output, ""); +} + +bool Filter::parseLine(StringRef Line) { + Line = Line.ltrim(); + + if (!Line.consume_front("#")) { + // A normal content line, filtered according to the current mode. + return Outputting; + } + + // Found a preprocessing directive line. From here on, we always return + // false since the preprocessing directives should be filtered out. + + Line.consume_front("line"); + if (!Line.startswith(" ")) + return false; // Not a line directive (pragma etc). + + // #line 123 "path/file.h" + // # 123 "path/file.h" 1 + + Line = + Line.ltrim(); // There could be multiple spaces after the #line directive + + size_t N; + if (Line.consumeInteger(10, N)) // Returns true to signify an error + return false; + + Line = Line.ltrim(); + + if (!Line.consume_front("\"")) + return false; // Malformed line, no quote found. + + // Split the string at the last quote (in case the path name had + // escaped quotes as well). + Line = Line.rsplit('"').first; + + StringRef Ext = Line.rsplit('.').second; + + if (Ext.equals_lower("h") || Ext.equals_lower("c")) { + Outputting = false; + } else { + Outputting = true; + } + + return false; +} + +bool Filter::streamEof() const { return Pos == DataLength; } + +} // anonymous namespace + +namespace llvm { + +std::string filterCppOutput(StringRef Input) { return Filter(Input).run(); } + +} // namespace llvm diff --git a/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptCppFilter.h b/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptCppFilter.h index 754dace070..780db317c9 100644 --- a/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptCppFilter.h +++ b/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptCppFilter.h @@ -1,34 +1,34 @@ -//===-- ResourceScriptCppFilter.h ------------------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===---------------------------------------------------------------------===// -// -// This filters the input to llvm-rc for preprocessor markers, removing -// preprocessing directives that a preprocessor can output or leave behind. -// -// It also filters out any contribution from files named *.h or *.c, based -// on preprocessor line markers. When preprocessing RC files, the included -// headers can leave behind C declarations, that RC doesn't understand. -// Rc.exe simply discards anything that comes from files named *.h or *.h. -// -// https://msdn.microsoft.com/en-us/library/windows/desktop/aa381033(v=vs.85).aspx -// -//===---------------------------------------------------------------------===// - -#ifndef LLVM_TOOLS_LLVMRC_RESOURCESCRIPTCPPFILTER_H -#define LLVM_TOOLS_LLVMRC_RESOURCESCRIPTCPPFILTER_H - -#include "llvm/ADT/StringRef.h" - -#include <string> - -namespace llvm { - -std::string filterCppOutput(StringRef Input); - -} // namespace llvm - -#endif +//===-- ResourceScriptCppFilter.h ------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// +// +// This filters the input to llvm-rc for preprocessor markers, removing +// preprocessing directives that a preprocessor can output or leave behind. +// +// It also filters out any contribution from files named *.h or *.c, based +// on preprocessor line markers. When preprocessing RC files, the included +// headers can leave behind C declarations, that RC doesn't understand. +// Rc.exe simply discards anything that comes from files named *.h or *.h. +// +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa381033(v=vs.85).aspx +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_TOOLS_LLVMRC_RESOURCESCRIPTCPPFILTER_H +#define LLVM_TOOLS_LLVMRC_RESOURCESCRIPTCPPFILTER_H + +#include "llvm/ADT/StringRef.h" + +#include <string> + +namespace llvm { + +std::string filterCppOutput(StringRef Input); + +} // namespace llvm + +#endif diff --git a/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptParser.cpp b/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptParser.cpp index 1dbfc5e8bf..5141ac0c38 100644 --- a/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptParser.cpp +++ b/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptParser.cpp @@ -1,857 +1,857 @@ -//===-- ResourceScriptParser.cpp --------------------------------*- C++-*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===---------------------------------------------------------------------===// -// -// This implements the parser defined in ResourceScriptParser.h. -// -//===---------------------------------------------------------------------===// - -#include "ResourceScriptParser.h" -#include "llvm/Option/ArgList.h" -#include "llvm/Support/FileSystem.h" -#include "llvm/Support/Path.h" -#include "llvm/Support/Process.h" - -// Take an expression returning llvm::Error and forward the error if it exists. -#define RETURN_IF_ERROR(Expr) \ - if (auto Err = (Expr)) \ - return std::move(Err); - -// Take an expression returning llvm::Expected<T> and assign it to Var or -// forward the error out of the function. -#define ASSIGN_OR_RETURN(Var, Expr) \ - auto Var = (Expr); \ - if (!Var) \ - return Var.takeError(); - -namespace llvm { -namespace rc { - -RCParser::ParserError::ParserError(const Twine &Expected, const LocIter CurLoc, - const LocIter End) - : ErrorLoc(CurLoc), FileEnd(End) { - CurMessage = "Error parsing file: expected " + Expected.str() + ", got " + - (CurLoc == End ? "<EOF>" : CurLoc->value()).str(); -} - -char RCParser::ParserError::ID = 0; - -RCParser::RCParser(std::vector<RCToken> TokenList) - : Tokens(std::move(TokenList)), CurLoc(Tokens.begin()), End(Tokens.end()) {} - -bool RCParser::isEof() const { return CurLoc == End; } - -RCParser::ParseType RCParser::parseSingleResource() { - // The first thing we read is usually a resource's name. However, in some - // cases (LANGUAGE and STRINGTABLE) the resources don't have their names - // and the first token to be read is the type. - ASSIGN_OR_RETURN(NameToken, readTypeOrName()); - - if (NameToken->equalsLower("LANGUAGE")) - return parseLanguageResource(); - else if (NameToken->equalsLower("STRINGTABLE")) - return parseStringTableResource(); - - // If it's not an unnamed resource, what we've just read is a name. Now, - // read resource type; - ASSIGN_OR_RETURN(TypeToken, readTypeOrName()); - - ParseType Result = std::unique_ptr<RCResource>(); - (void)!Result; - - if (TypeToken->equalsLower("ACCELERATORS")) - Result = parseAcceleratorsResource(); - else if (TypeToken->equalsLower("BITMAP")) - Result = parseBitmapResource(); - else if (TypeToken->equalsLower("CURSOR")) - Result = parseCursorResource(); - else if (TypeToken->equalsLower("DIALOG")) - Result = parseDialogResource(false); - else if (TypeToken->equalsLower("DIALOGEX")) - Result = parseDialogResource(true); - else if (TypeToken->equalsLower("HTML")) - Result = parseHTMLResource(); - else if (TypeToken->equalsLower("ICON")) - Result = parseIconResource(); - else if (TypeToken->equalsLower("MENU")) - Result = parseMenuResource(); - else if (TypeToken->equalsLower("RCDATA")) - Result = parseUserDefinedResource(RkRcData); - else if (TypeToken->equalsLower("VERSIONINFO")) - Result = parseVersionInfoResource(); - else - Result = parseUserDefinedResource(*TypeToken); - - if (Result) - (*Result)->setName(*NameToken); - - return Result; -} - -bool RCParser::isNextTokenKind(Kind TokenKind) const { - return !isEof() && look().kind() == TokenKind; -} - -const RCToken &RCParser::look() const { - assert(!isEof()); - return *CurLoc; -} - -const RCToken &RCParser::read() { - assert(!isEof()); - return *CurLoc++; -} - -void RCParser::consume() { - assert(!isEof()); - CurLoc++; -} - -// An integer description might consist of a single integer or -// an arithmetic expression evaluating to the integer. The expressions -// can contain the following tokens: <int> ( ) + - | & ~ not. Their meaning -// is the same as in C++ except for 'not' expression. -// The operators in the original RC implementation have the following -// precedence: -// 1) Unary operators (- ~ not), -// 2) Binary operators (+ - & |), with no precedence. -// -// 'not' expression is mostly useful for style values. It evaluates to 0, -// but value given to the operator is stored separately from integer value. -// It's mostly useful for control style expressions and causes bits from -// default control style to be excluded from generated style. For binary -// operators the mask from the right operand is applied to the left operand -// and masks from both operands are combined in operator result. -// -// The following grammar is used to parse the expressions Exp1: -// Exp1 ::= Exp2 || Exp1 + Exp2 || Exp1 - Exp2 || Exp1 | Exp2 || Exp1 & Exp2 -// Exp2 ::= -Exp2 || ~Exp2 || not Expr2 || Int || (Exp1). -// (More conveniently, Exp1 is a non-empty sequence of Exp2 expressions, -// separated by binary operators.) -// -// Expressions of type Exp1 are read by parseIntExpr1(Inner) method, while Exp2 -// is read by parseIntExpr2(). -// -// The original Microsoft tool handles multiple unary operators incorrectly. -// For example, in 16-bit little-endian integers: -// 1 => 01 00, -1 => ff ff, --1 => ff ff, ---1 => 01 00; -// 1 => 01 00, ~1 => fe ff, ~~1 => fd ff, ~~~1 => fc ff. -// Our implementation differs from the original one and handles these -// operators correctly: -// 1 => 01 00, -1 => ff ff, --1 => 01 00, ---1 => ff ff; -// 1 => 01 00, ~1 => fe ff, ~~1 => 01 00, ~~~1 => fe ff. - -Expected<RCInt> RCParser::readInt() { - ASSIGN_OR_RETURN(Value, parseIntExpr1()); - return (*Value).getValue(); -} - -Expected<IntWithNotMask> RCParser::parseIntExpr1() { - // Exp1 ::= Exp2 || Exp1 + Exp2 || Exp1 - Exp2 || Exp1 | Exp2 || Exp1 & Exp2. - ASSIGN_OR_RETURN(FirstResult, parseIntExpr2()); - IntWithNotMask Result = *FirstResult; - - while (!isEof() && look().isBinaryOp()) { - auto OpToken = read(); - ASSIGN_OR_RETURN(NextResult, parseIntExpr2()); - - switch (OpToken.kind()) { - case Kind::Plus: - Result += *NextResult; - break; - - case Kind::Minus: - Result -= *NextResult; - break; - - case Kind::Pipe: - Result |= *NextResult; - break; - - case Kind::Amp: - Result &= *NextResult; - break; - - default: - llvm_unreachable("Already processed all binary ops."); - } - } - - return Result; -} - -Expected<IntWithNotMask> RCParser::parseIntExpr2() { - // Exp2 ::= -Exp2 || ~Exp2 || not Expr2 || Int || (Exp1). - static const char ErrorMsg[] = "'-', '~', integer or '('"; - - if (isEof()) - return getExpectedError(ErrorMsg); - - switch (look().kind()) { - case Kind::Minus: { - consume(); - ASSIGN_OR_RETURN(Result, parseIntExpr2()); - return -(*Result); - } - - case Kind::Tilde: { - consume(); - ASSIGN_OR_RETURN(Result, parseIntExpr2()); - return ~(*Result); - } - - case Kind::Int: - return RCInt(read()); - - case Kind::LeftParen: { - consume(); - ASSIGN_OR_RETURN(Result, parseIntExpr1()); - RETURN_IF_ERROR(consumeType(Kind::RightParen)); - return *Result; - } - - case Kind::Identifier: { - if (!read().value().equals_lower("not")) - return getExpectedError(ErrorMsg, true); - ASSIGN_OR_RETURN(Result, parseIntExpr2()); - return IntWithNotMask(0, (*Result).getValue()); - } - - default: - return getExpectedError(ErrorMsg); - } -} - -Expected<StringRef> RCParser::readString() { - if (!isNextTokenKind(Kind::String)) - return getExpectedError("string"); - return read().value(); -} - -Expected<StringRef> RCParser::readFilename() { - if (!isNextTokenKind(Kind::String) && !isNextTokenKind(Kind::Identifier)) - return getExpectedError("string"); - return read().value(); -} - -Expected<StringRef> RCParser::readIdentifier() { - if (!isNextTokenKind(Kind::Identifier)) - return getExpectedError("identifier"); - return read().value(); -} - -Expected<IntOrString> RCParser::readIntOrString() { - if (!isNextTokenKind(Kind::Int) && !isNextTokenKind(Kind::String)) - return getExpectedError("int or string"); - return IntOrString(read()); -} - -Expected<IntOrString> RCParser::readTypeOrName() { - // We suggest that the correct resource name or type should be either an - // identifier or an integer. The original RC tool is much more liberal. - if (!isNextTokenKind(Kind::Identifier) && !isNextTokenKind(Kind::Int)) - return getExpectedError("int or identifier"); - return IntOrString(read()); -} - -Error RCParser::consumeType(Kind TokenKind) { - if (isNextTokenKind(TokenKind)) { - consume(); - return Error::success(); - } - - switch (TokenKind) { -#define TOKEN(TokenName) \ - case Kind::TokenName: \ - return getExpectedError(#TokenName); -#define SHORT_TOKEN(TokenName, TokenCh) \ - case Kind::TokenName: \ - return getExpectedError(#TokenCh); -#include "ResourceScriptTokenList.def" - } - - llvm_unreachable("All case options exhausted."); -} - -bool RCParser::consumeOptionalType(Kind TokenKind) { - if (isNextTokenKind(TokenKind)) { - consume(); - return true; - } - - return false; -} - -Expected<SmallVector<RCInt, 8>> RCParser::readIntsWithCommas(size_t MinCount, - size_t MaxCount) { - assert(MinCount <= MaxCount); - - SmallVector<RCInt, 8> Result; - - auto FailureHandler = - [&](llvm::Error Err) -> Expected<SmallVector<RCInt, 8>> { - if (Result.size() < MinCount) - return std::move(Err); - consumeError(std::move(Err)); - return Result; - }; - - for (size_t i = 0; i < MaxCount; ++i) { - // Try to read a comma unless we read the first token. - // Sometimes RC tool requires them and sometimes not. We decide to - // always require them. - if (i >= 1) { - if (auto CommaError = consumeType(Kind::Comma)) - return FailureHandler(std::move(CommaError)); - } - - if (auto IntResult = readInt()) - Result.push_back(*IntResult); - else - return FailureHandler(IntResult.takeError()); - } - - return std::move(Result); -} - -Expected<uint32_t> RCParser::parseFlags(ArrayRef<StringRef> FlagDesc, - ArrayRef<uint32_t> FlagValues) { - assert(!FlagDesc.empty()); - assert(FlagDesc.size() == FlagValues.size()); - - uint32_t Result = 0; - while (isNextTokenKind(Kind::Comma)) { - consume(); - ASSIGN_OR_RETURN(FlagResult, readIdentifier()); - bool FoundFlag = false; - - for (size_t FlagId = 0; FlagId < FlagDesc.size(); ++FlagId) { - if (!FlagResult->equals_lower(FlagDesc[FlagId])) - continue; - - Result |= FlagValues[FlagId]; - FoundFlag = true; - break; - } - - if (!FoundFlag) - return getExpectedError(join(FlagDesc, "/"), true); - } - - return Result; -} - -uint16_t RCParser::parseMemoryFlags(uint16_t Flags) { - while (!isEof()) { - const RCToken &Token = look(); - if (Token.kind() != Kind::Identifier) - return Flags; - const StringRef Ident = Token.value(); - if (Ident.equals_lower("PRELOAD")) - Flags |= MfPreload; - else if (Ident.equals_lower("LOADONCALL")) - Flags &= ~MfPreload; - else if (Ident.equals_lower("FIXED")) - Flags &= ~(MfMoveable | MfDiscardable); - else if (Ident.equals_lower("MOVEABLE")) - Flags |= MfMoveable; - else if (Ident.equals_lower("DISCARDABLE")) - Flags |= MfDiscardable | MfMoveable | MfPure; - else if (Ident.equals_lower("PURE")) - Flags |= MfPure; - else if (Ident.equals_lower("IMPURE")) - Flags &= ~(MfPure | MfDiscardable); - else if (Ident.equals_lower("SHARED")) - Flags |= MfPure; - else if (Ident.equals_lower("NONSHARED")) - Flags &= ~(MfPure | MfDiscardable); - else - return Flags; - consume(); - } - return Flags; -} - -Expected<OptionalStmtList> -RCParser::parseOptionalStatements(OptStmtType StmtsType) { - OptionalStmtList Result; - - // The last statement is always followed by the start of the block. - while (!isNextTokenKind(Kind::BlockBegin)) { - ASSIGN_OR_RETURN(SingleParse, parseSingleOptionalStatement(StmtsType)); - Result.addStmt(std::move(*SingleParse)); - } - - return std::move(Result); -} - -Expected<std::unique_ptr<OptionalStmt>> -RCParser::parseSingleOptionalStatement(OptStmtType StmtsType) { - ASSIGN_OR_RETURN(TypeToken, readIdentifier()); - if (TypeToken->equals_lower("CHARACTERISTICS")) - return parseCharacteristicsStmt(); - if (TypeToken->equals_lower("LANGUAGE")) - return parseLanguageStmt(); - if (TypeToken->equals_lower("VERSION")) - return parseVersionStmt(); - - if (StmtsType != OptStmtType::BasicStmt) { - if (TypeToken->equals_lower("CAPTION")) - return parseCaptionStmt(); - if (TypeToken->equals_lower("CLASS")) - return parseClassStmt(); - if (TypeToken->equals_lower("EXSTYLE")) - return parseExStyleStmt(); - if (TypeToken->equals_lower("FONT")) - return parseFontStmt(StmtsType); - if (TypeToken->equals_lower("STYLE")) - return parseStyleStmt(); - } - - return getExpectedError("optional statement type, BEGIN or '{'", - /* IsAlreadyRead = */ true); -} - -RCParser::ParseType RCParser::parseLanguageResource() { - // Read LANGUAGE as an optional statement. If it's read correctly, we can - // upcast it to RCResource. - return parseLanguageStmt(); -} - -RCParser::ParseType RCParser::parseAcceleratorsResource() { - uint16_t MemoryFlags = - parseMemoryFlags(AcceleratorsResource::getDefaultMemoryFlags()); - ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements()); - RETURN_IF_ERROR(consumeType(Kind::BlockBegin)); - - auto Accels = std::make_unique<AcceleratorsResource>( - std::move(*OptStatements), MemoryFlags); - - while (!consumeOptionalType(Kind::BlockEnd)) { - ASSIGN_OR_RETURN(EventResult, readIntOrString()); - RETURN_IF_ERROR(consumeType(Kind::Comma)); - ASSIGN_OR_RETURN(IDResult, readInt()); - ASSIGN_OR_RETURN( - FlagsResult, - parseFlags(AcceleratorsResource::Accelerator::OptionsStr, - AcceleratorsResource::Accelerator::OptionsFlags)); - Accels->addAccelerator(*EventResult, *IDResult, *FlagsResult); - } - - return std::move(Accels); -} - -RCParser::ParseType RCParser::parseCursorResource() { - uint16_t MemoryFlags = - parseMemoryFlags(CursorResource::getDefaultMemoryFlags()); - ASSIGN_OR_RETURN(Arg, readFilename()); - return std::make_unique<CursorResource>(*Arg, MemoryFlags); -} - -RCParser::ParseType RCParser::parseDialogResource(bool IsExtended) { - uint16_t MemoryFlags = - parseMemoryFlags(DialogResource::getDefaultMemoryFlags()); - // Dialog resources have the following format of the arguments: - // DIALOG: x, y, width, height [opt stmts...] {controls...} - // DIALOGEX: x, y, width, height [, helpID] [opt stmts...] {controls...} - // These are very similar, so we parse them together. - ASSIGN_OR_RETURN(LocResult, readIntsWithCommas(4, 4)); - - uint32_t HelpID = 0; // When HelpID is unset, it's assumed to be 0. - if (IsExtended && consumeOptionalType(Kind::Comma)) { - ASSIGN_OR_RETURN(HelpIDResult, readInt()); - HelpID = *HelpIDResult; - } - - ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements( - IsExtended ? OptStmtType::DialogExStmt - : OptStmtType::DialogStmt)); - - assert(isNextTokenKind(Kind::BlockBegin) && - "parseOptionalStatements, when successful, halts on BlockBegin."); - consume(); - - auto Dialog = std::make_unique<DialogResource>( - (*LocResult)[0], (*LocResult)[1], (*LocResult)[2], (*LocResult)[3], - HelpID, std::move(*OptStatements), IsExtended, MemoryFlags); - - while (!consumeOptionalType(Kind::BlockEnd)) { - ASSIGN_OR_RETURN(ControlDefResult, parseControl()); - Dialog->addControl(std::move(*ControlDefResult)); - } - - return std::move(Dialog); -} - -RCParser::ParseType RCParser::parseUserDefinedResource(IntOrString Type) { - uint16_t MemoryFlags = - parseMemoryFlags(UserDefinedResource::getDefaultMemoryFlags()); - if (isEof()) - return getExpectedError("filename, '{' or BEGIN"); - - // Check if this is a file resource. - switch (look().kind()) { - case Kind::String: - case Kind::Identifier: - return std::make_unique<UserDefinedResource>(Type, read().value(), - MemoryFlags); - default: - break; - } - - RETURN_IF_ERROR(consumeType(Kind::BlockBegin)); - std::vector<IntOrString> Data; - - // Consume comma before each consecutive token except the first one. - bool ConsumeComma = false; - while (!consumeOptionalType(Kind::BlockEnd)) { - if (ConsumeComma) - RETURN_IF_ERROR(consumeType(Kind::Comma)); - ConsumeComma = true; - - ASSIGN_OR_RETURN(Item, readIntOrString()); - Data.push_back(*Item); - } - - return std::make_unique<UserDefinedResource>(Type, std::move(Data), - MemoryFlags); -} - -RCParser::ParseType RCParser::parseVersionInfoResource() { - uint16_t MemoryFlags = - parseMemoryFlags(VersionInfoResource::getDefaultMemoryFlags()); - ASSIGN_OR_RETURN(FixedResult, parseVersionInfoFixed()); - ASSIGN_OR_RETURN(BlockResult, parseVersionInfoBlockContents(StringRef())); - return std::make_unique<VersionInfoResource>( - std::move(**BlockResult), std::move(*FixedResult), MemoryFlags); -} - -Expected<Control> RCParser::parseControl() { - // Each control definition (except CONTROL) follows one of the schemes below - // depending on the control class: - // [class] text, id, x, y, width, height [, style] [, exstyle] [, helpID] - // [class] id, x, y, width, height [, style] [, exstyle] [, helpID] - // Note that control ids must be integers. - // Text might be either a string or an integer pointing to resource ID. - ASSIGN_OR_RETURN(ClassResult, readIdentifier()); - std::string ClassUpper = ClassResult->upper(); - auto CtlInfo = Control::SupportedCtls.find(ClassUpper); - if (CtlInfo == Control::SupportedCtls.end()) - return getExpectedError("control type, END or '}'", true); - - // Read caption if necessary. - IntOrString Caption{StringRef()}; - if (CtlInfo->getValue().HasTitle) { - ASSIGN_OR_RETURN(CaptionResult, readIntOrString()); - RETURN_IF_ERROR(consumeType(Kind::Comma)); - Caption = *CaptionResult; - } - - ASSIGN_OR_RETURN(ID, readInt()); - RETURN_IF_ERROR(consumeType(Kind::Comma)); - - IntOrString Class; - Optional<IntWithNotMask> Style; - if (ClassUpper == "CONTROL") { - // CONTROL text, id, class, style, x, y, width, height [, exstyle] [, helpID] - ASSIGN_OR_RETURN(ClassStr, readString()); - RETURN_IF_ERROR(consumeType(Kind::Comma)); - Class = *ClassStr; - ASSIGN_OR_RETURN(StyleVal, parseIntExpr1()); - RETURN_IF_ERROR(consumeType(Kind::Comma)); - Style = *StyleVal; - } else { - Class = CtlInfo->getValue().CtlClass; - } - - // x, y, width, height - ASSIGN_OR_RETURN(Args, readIntsWithCommas(4, 4)); - - if (ClassUpper != "CONTROL") { - if (consumeOptionalType(Kind::Comma)) { - ASSIGN_OR_RETURN(Val, parseIntExpr1()); - Style = *Val; - } - } - - Optional<uint32_t> ExStyle; - if (consumeOptionalType(Kind::Comma)) { - ASSIGN_OR_RETURN(Val, readInt()); - ExStyle = *Val; - } - Optional<uint32_t> HelpID; - if (consumeOptionalType(Kind::Comma)) { - ASSIGN_OR_RETURN(Val, readInt()); - HelpID = *Val; - } - - return Control(*ClassResult, Caption, *ID, (*Args)[0], (*Args)[1], - (*Args)[2], (*Args)[3], Style, ExStyle, HelpID, Class); -} - -RCParser::ParseType RCParser::parseBitmapResource() { - uint16_t MemoryFlags = - parseMemoryFlags(BitmapResource::getDefaultMemoryFlags()); - ASSIGN_OR_RETURN(Arg, readFilename()); - return std::make_unique<BitmapResource>(*Arg, MemoryFlags); -} - -RCParser::ParseType RCParser::parseIconResource() { - uint16_t MemoryFlags = - parseMemoryFlags(IconResource::getDefaultMemoryFlags()); - ASSIGN_OR_RETURN(Arg, readFilename()); - return std::make_unique<IconResource>(*Arg, MemoryFlags); -} - -RCParser::ParseType RCParser::parseHTMLResource() { - uint16_t MemoryFlags = - parseMemoryFlags(HTMLResource::getDefaultMemoryFlags()); - ASSIGN_OR_RETURN(Arg, readFilename()); - return std::make_unique<HTMLResource>(*Arg, MemoryFlags); -} - -RCParser::ParseType RCParser::parseMenuResource() { - uint16_t MemoryFlags = - parseMemoryFlags(MenuResource::getDefaultMemoryFlags()); - ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements()); - ASSIGN_OR_RETURN(Items, parseMenuItemsList()); - return std::make_unique<MenuResource>(std::move(*OptStatements), - std::move(*Items), MemoryFlags); -} - -Expected<MenuDefinitionList> RCParser::parseMenuItemsList() { - RETURN_IF_ERROR(consumeType(Kind::BlockBegin)); - - MenuDefinitionList List; - - // Read a set of items. Each item is of one of three kinds: - // MENUITEM SEPARATOR - // MENUITEM caption:String, result:Int [, menu flags]... - // POPUP caption:String [, menu flags]... { items... } - while (!consumeOptionalType(Kind::BlockEnd)) { - ASSIGN_OR_RETURN(ItemTypeResult, readIdentifier()); - - bool IsMenuItem = ItemTypeResult->equals_lower("MENUITEM"); - bool IsPopup = ItemTypeResult->equals_lower("POPUP"); - if (!IsMenuItem && !IsPopup) - return getExpectedError("MENUITEM, POPUP, END or '}'", true); - - if (IsMenuItem && isNextTokenKind(Kind::Identifier)) { - // Now, expecting SEPARATOR. - ASSIGN_OR_RETURN(SeparatorResult, readIdentifier()); - if (SeparatorResult->equals_lower("SEPARATOR")) { - List.addDefinition(std::make_unique<MenuSeparator>()); - continue; - } - - return getExpectedError("SEPARATOR or string", true); - } - - // Not a separator. Read the caption. - ASSIGN_OR_RETURN(CaptionResult, readString()); - - // If MENUITEM, expect also a comma and an integer. - uint32_t MenuResult = -1; - - if (IsMenuItem) { - RETURN_IF_ERROR(consumeType(Kind::Comma)); - ASSIGN_OR_RETURN(IntResult, readInt()); - MenuResult = *IntResult; - } - - ASSIGN_OR_RETURN(FlagsResult, parseFlags(MenuDefinition::OptionsStr, - MenuDefinition::OptionsFlags)); - - if (IsPopup) { - // If POPUP, read submenu items recursively. - ASSIGN_OR_RETURN(SubMenuResult, parseMenuItemsList()); - List.addDefinition(std::make_unique<PopupItem>( - *CaptionResult, *FlagsResult, std::move(*SubMenuResult))); - continue; - } - - assert(IsMenuItem); - List.addDefinition( - std::make_unique<MenuItem>(*CaptionResult, MenuResult, *FlagsResult)); - } - - return std::move(List); -} - -RCParser::ParseType RCParser::parseStringTableResource() { - uint16_t MemoryFlags = - parseMemoryFlags(StringTableResource::getDefaultMemoryFlags()); - ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements()); - RETURN_IF_ERROR(consumeType(Kind::BlockBegin)); - - auto Table = std::make_unique<StringTableResource>(std::move(*OptStatements), - MemoryFlags); - - // Read strings until we reach the end of the block. - while (!consumeOptionalType(Kind::BlockEnd)) { - // Each definition consists of string's ID (an integer) and a string. - // Some examples in documentation suggest that there might be a comma in - // between, however we strictly adhere to the single statement definition. - ASSIGN_OR_RETURN(IDResult, readInt()); - consumeOptionalType(Kind::Comma); - - std::vector<StringRef> Strings; - ASSIGN_OR_RETURN(StrResult, readString()); - Strings.push_back(*StrResult); - while (isNextTokenKind(Kind::String)) - Strings.push_back(read().value()); - - Table->addStrings(*IDResult, std::move(Strings)); - } - - return std::move(Table); -} - -Expected<std::unique_ptr<VersionInfoBlock>> -RCParser::parseVersionInfoBlockContents(StringRef BlockName) { - RETURN_IF_ERROR(consumeType(Kind::BlockBegin)); - - auto Contents = std::make_unique<VersionInfoBlock>(BlockName); - - while (!isNextTokenKind(Kind::BlockEnd)) { - ASSIGN_OR_RETURN(Stmt, parseVersionInfoStmt()); - Contents->addStmt(std::move(*Stmt)); - } - - consume(); // Consume BlockEnd. - - return std::move(Contents); -} - -Expected<std::unique_ptr<VersionInfoStmt>> RCParser::parseVersionInfoStmt() { - // Expect either BLOCK or VALUE, then a name or a key (a string). - ASSIGN_OR_RETURN(TypeResult, readIdentifier()); - - if (TypeResult->equals_lower("BLOCK")) { - ASSIGN_OR_RETURN(NameResult, readString()); - return parseVersionInfoBlockContents(*NameResult); - } - - if (TypeResult->equals_lower("VALUE")) { - ASSIGN_OR_RETURN(KeyResult, readString()); - // Read a non-empty list of strings and/or ints, each - // possibly preceded by a comma. Unfortunately, the tool behavior depends - // on them existing or not, so we need to memorize where we found them. - std::vector<IntOrString> Values; - std::vector<bool> PrecedingCommas; - RETURN_IF_ERROR(consumeType(Kind::Comma)); - while (!isNextTokenKind(Kind::Identifier) && - !isNextTokenKind(Kind::BlockEnd)) { - // Try to eat a comma if it's not the first statement. - bool HadComma = Values.size() > 0 && consumeOptionalType(Kind::Comma); - ASSIGN_OR_RETURN(ValueResult, readIntOrString()); - Values.push_back(*ValueResult); - PrecedingCommas.push_back(HadComma); - } - return std::make_unique<VersionInfoValue>(*KeyResult, std::move(Values), - std::move(PrecedingCommas)); - } - - return getExpectedError("BLOCK or VALUE", true); -} - -Expected<VersionInfoResource::VersionInfoFixed> -RCParser::parseVersionInfoFixed() { - using RetType = VersionInfoResource::VersionInfoFixed; - RetType Result; - - // Read until the beginning of the block. - while (!isNextTokenKind(Kind::BlockBegin)) { - ASSIGN_OR_RETURN(TypeResult, readIdentifier()); - auto FixedType = RetType::getFixedType(*TypeResult); - - if (!RetType::isTypeSupported(FixedType)) - return getExpectedError("fixed VERSIONINFO statement type", true); - if (Result.IsTypePresent[FixedType]) - return getExpectedError("yet unread fixed VERSIONINFO statement type", - true); - - // VERSION variations take multiple integers. - size_t NumInts = RetType::isVersionType(FixedType) ? 4 : 1; +//===-- ResourceScriptParser.cpp --------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// +// +// This implements the parser defined in ResourceScriptParser.h. +// +//===---------------------------------------------------------------------===// + +#include "ResourceScriptParser.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Process.h" + +// Take an expression returning llvm::Error and forward the error if it exists. +#define RETURN_IF_ERROR(Expr) \ + if (auto Err = (Expr)) \ + return std::move(Err); + +// Take an expression returning llvm::Expected<T> and assign it to Var or +// forward the error out of the function. +#define ASSIGN_OR_RETURN(Var, Expr) \ + auto Var = (Expr); \ + if (!Var) \ + return Var.takeError(); + +namespace llvm { +namespace rc { + +RCParser::ParserError::ParserError(const Twine &Expected, const LocIter CurLoc, + const LocIter End) + : ErrorLoc(CurLoc), FileEnd(End) { + CurMessage = "Error parsing file: expected " + Expected.str() + ", got " + + (CurLoc == End ? "<EOF>" : CurLoc->value()).str(); +} + +char RCParser::ParserError::ID = 0; + +RCParser::RCParser(std::vector<RCToken> TokenList) + : Tokens(std::move(TokenList)), CurLoc(Tokens.begin()), End(Tokens.end()) {} + +bool RCParser::isEof() const { return CurLoc == End; } + +RCParser::ParseType RCParser::parseSingleResource() { + // The first thing we read is usually a resource's name. However, in some + // cases (LANGUAGE and STRINGTABLE) the resources don't have their names + // and the first token to be read is the type. + ASSIGN_OR_RETURN(NameToken, readTypeOrName()); + + if (NameToken->equalsLower("LANGUAGE")) + return parseLanguageResource(); + else if (NameToken->equalsLower("STRINGTABLE")) + return parseStringTableResource(); + + // If it's not an unnamed resource, what we've just read is a name. Now, + // read resource type; + ASSIGN_OR_RETURN(TypeToken, readTypeOrName()); + + ParseType Result = std::unique_ptr<RCResource>(); + (void)!Result; + + if (TypeToken->equalsLower("ACCELERATORS")) + Result = parseAcceleratorsResource(); + else if (TypeToken->equalsLower("BITMAP")) + Result = parseBitmapResource(); + else if (TypeToken->equalsLower("CURSOR")) + Result = parseCursorResource(); + else if (TypeToken->equalsLower("DIALOG")) + Result = parseDialogResource(false); + else if (TypeToken->equalsLower("DIALOGEX")) + Result = parseDialogResource(true); + else if (TypeToken->equalsLower("HTML")) + Result = parseHTMLResource(); + else if (TypeToken->equalsLower("ICON")) + Result = parseIconResource(); + else if (TypeToken->equalsLower("MENU")) + Result = parseMenuResource(); + else if (TypeToken->equalsLower("RCDATA")) + Result = parseUserDefinedResource(RkRcData); + else if (TypeToken->equalsLower("VERSIONINFO")) + Result = parseVersionInfoResource(); + else + Result = parseUserDefinedResource(*TypeToken); + + if (Result) + (*Result)->setName(*NameToken); + + return Result; +} + +bool RCParser::isNextTokenKind(Kind TokenKind) const { + return !isEof() && look().kind() == TokenKind; +} + +const RCToken &RCParser::look() const { + assert(!isEof()); + return *CurLoc; +} + +const RCToken &RCParser::read() { + assert(!isEof()); + return *CurLoc++; +} + +void RCParser::consume() { + assert(!isEof()); + CurLoc++; +} + +// An integer description might consist of a single integer or +// an arithmetic expression evaluating to the integer. The expressions +// can contain the following tokens: <int> ( ) + - | & ~ not. Their meaning +// is the same as in C++ except for 'not' expression. +// The operators in the original RC implementation have the following +// precedence: +// 1) Unary operators (- ~ not), +// 2) Binary operators (+ - & |), with no precedence. +// +// 'not' expression is mostly useful for style values. It evaluates to 0, +// but value given to the operator is stored separately from integer value. +// It's mostly useful for control style expressions and causes bits from +// default control style to be excluded from generated style. For binary +// operators the mask from the right operand is applied to the left operand +// and masks from both operands are combined in operator result. +// +// The following grammar is used to parse the expressions Exp1: +// Exp1 ::= Exp2 || Exp1 + Exp2 || Exp1 - Exp2 || Exp1 | Exp2 || Exp1 & Exp2 +// Exp2 ::= -Exp2 || ~Exp2 || not Expr2 || Int || (Exp1). +// (More conveniently, Exp1 is a non-empty sequence of Exp2 expressions, +// separated by binary operators.) +// +// Expressions of type Exp1 are read by parseIntExpr1(Inner) method, while Exp2 +// is read by parseIntExpr2(). +// +// The original Microsoft tool handles multiple unary operators incorrectly. +// For example, in 16-bit little-endian integers: +// 1 => 01 00, -1 => ff ff, --1 => ff ff, ---1 => 01 00; +// 1 => 01 00, ~1 => fe ff, ~~1 => fd ff, ~~~1 => fc ff. +// Our implementation differs from the original one and handles these +// operators correctly: +// 1 => 01 00, -1 => ff ff, --1 => 01 00, ---1 => ff ff; +// 1 => 01 00, ~1 => fe ff, ~~1 => 01 00, ~~~1 => fe ff. + +Expected<RCInt> RCParser::readInt() { + ASSIGN_OR_RETURN(Value, parseIntExpr1()); + return (*Value).getValue(); +} + +Expected<IntWithNotMask> RCParser::parseIntExpr1() { + // Exp1 ::= Exp2 || Exp1 + Exp2 || Exp1 - Exp2 || Exp1 | Exp2 || Exp1 & Exp2. + ASSIGN_OR_RETURN(FirstResult, parseIntExpr2()); + IntWithNotMask Result = *FirstResult; + + while (!isEof() && look().isBinaryOp()) { + auto OpToken = read(); + ASSIGN_OR_RETURN(NextResult, parseIntExpr2()); + + switch (OpToken.kind()) { + case Kind::Plus: + Result += *NextResult; + break; + + case Kind::Minus: + Result -= *NextResult; + break; + + case Kind::Pipe: + Result |= *NextResult; + break; + + case Kind::Amp: + Result &= *NextResult; + break; + + default: + llvm_unreachable("Already processed all binary ops."); + } + } + + return Result; +} + +Expected<IntWithNotMask> RCParser::parseIntExpr2() { + // Exp2 ::= -Exp2 || ~Exp2 || not Expr2 || Int || (Exp1). + static const char ErrorMsg[] = "'-', '~', integer or '('"; + + if (isEof()) + return getExpectedError(ErrorMsg); + + switch (look().kind()) { + case Kind::Minus: { + consume(); + ASSIGN_OR_RETURN(Result, parseIntExpr2()); + return -(*Result); + } + + case Kind::Tilde: { + consume(); + ASSIGN_OR_RETURN(Result, parseIntExpr2()); + return ~(*Result); + } + + case Kind::Int: + return RCInt(read()); + + case Kind::LeftParen: { + consume(); + ASSIGN_OR_RETURN(Result, parseIntExpr1()); + RETURN_IF_ERROR(consumeType(Kind::RightParen)); + return *Result; + } + + case Kind::Identifier: { + if (!read().value().equals_lower("not")) + return getExpectedError(ErrorMsg, true); + ASSIGN_OR_RETURN(Result, parseIntExpr2()); + return IntWithNotMask(0, (*Result).getValue()); + } + + default: + return getExpectedError(ErrorMsg); + } +} + +Expected<StringRef> RCParser::readString() { + if (!isNextTokenKind(Kind::String)) + return getExpectedError("string"); + return read().value(); +} + +Expected<StringRef> RCParser::readFilename() { + if (!isNextTokenKind(Kind::String) && !isNextTokenKind(Kind::Identifier)) + return getExpectedError("string"); + return read().value(); +} + +Expected<StringRef> RCParser::readIdentifier() { + if (!isNextTokenKind(Kind::Identifier)) + return getExpectedError("identifier"); + return read().value(); +} + +Expected<IntOrString> RCParser::readIntOrString() { + if (!isNextTokenKind(Kind::Int) && !isNextTokenKind(Kind::String)) + return getExpectedError("int or string"); + return IntOrString(read()); +} + +Expected<IntOrString> RCParser::readTypeOrName() { + // We suggest that the correct resource name or type should be either an + // identifier or an integer. The original RC tool is much more liberal. + if (!isNextTokenKind(Kind::Identifier) && !isNextTokenKind(Kind::Int)) + return getExpectedError("int or identifier"); + return IntOrString(read()); +} + +Error RCParser::consumeType(Kind TokenKind) { + if (isNextTokenKind(TokenKind)) { + consume(); + return Error::success(); + } + + switch (TokenKind) { +#define TOKEN(TokenName) \ + case Kind::TokenName: \ + return getExpectedError(#TokenName); +#define SHORT_TOKEN(TokenName, TokenCh) \ + case Kind::TokenName: \ + return getExpectedError(#TokenCh); +#include "ResourceScriptTokenList.def" + } + + llvm_unreachable("All case options exhausted."); +} + +bool RCParser::consumeOptionalType(Kind TokenKind) { + if (isNextTokenKind(TokenKind)) { + consume(); + return true; + } + + return false; +} + +Expected<SmallVector<RCInt, 8>> RCParser::readIntsWithCommas(size_t MinCount, + size_t MaxCount) { + assert(MinCount <= MaxCount); + + SmallVector<RCInt, 8> Result; + + auto FailureHandler = + [&](llvm::Error Err) -> Expected<SmallVector<RCInt, 8>> { + if (Result.size() < MinCount) + return std::move(Err); + consumeError(std::move(Err)); + return Result; + }; + + for (size_t i = 0; i < MaxCount; ++i) { + // Try to read a comma unless we read the first token. + // Sometimes RC tool requires them and sometimes not. We decide to + // always require them. + if (i >= 1) { + if (auto CommaError = consumeType(Kind::Comma)) + return FailureHandler(std::move(CommaError)); + } + + if (auto IntResult = readInt()) + Result.push_back(*IntResult); + else + return FailureHandler(IntResult.takeError()); + } + + return std::move(Result); +} + +Expected<uint32_t> RCParser::parseFlags(ArrayRef<StringRef> FlagDesc, + ArrayRef<uint32_t> FlagValues) { + assert(!FlagDesc.empty()); + assert(FlagDesc.size() == FlagValues.size()); + + uint32_t Result = 0; + while (isNextTokenKind(Kind::Comma)) { + consume(); + ASSIGN_OR_RETURN(FlagResult, readIdentifier()); + bool FoundFlag = false; + + for (size_t FlagId = 0; FlagId < FlagDesc.size(); ++FlagId) { + if (!FlagResult->equals_lower(FlagDesc[FlagId])) + continue; + + Result |= FlagValues[FlagId]; + FoundFlag = true; + break; + } + + if (!FoundFlag) + return getExpectedError(join(FlagDesc, "/"), true); + } + + return Result; +} + +uint16_t RCParser::parseMemoryFlags(uint16_t Flags) { + while (!isEof()) { + const RCToken &Token = look(); + if (Token.kind() != Kind::Identifier) + return Flags; + const StringRef Ident = Token.value(); + if (Ident.equals_lower("PRELOAD")) + Flags |= MfPreload; + else if (Ident.equals_lower("LOADONCALL")) + Flags &= ~MfPreload; + else if (Ident.equals_lower("FIXED")) + Flags &= ~(MfMoveable | MfDiscardable); + else if (Ident.equals_lower("MOVEABLE")) + Flags |= MfMoveable; + else if (Ident.equals_lower("DISCARDABLE")) + Flags |= MfDiscardable | MfMoveable | MfPure; + else if (Ident.equals_lower("PURE")) + Flags |= MfPure; + else if (Ident.equals_lower("IMPURE")) + Flags &= ~(MfPure | MfDiscardable); + else if (Ident.equals_lower("SHARED")) + Flags |= MfPure; + else if (Ident.equals_lower("NONSHARED")) + Flags &= ~(MfPure | MfDiscardable); + else + return Flags; + consume(); + } + return Flags; +} + +Expected<OptionalStmtList> +RCParser::parseOptionalStatements(OptStmtType StmtsType) { + OptionalStmtList Result; + + // The last statement is always followed by the start of the block. + while (!isNextTokenKind(Kind::BlockBegin)) { + ASSIGN_OR_RETURN(SingleParse, parseSingleOptionalStatement(StmtsType)); + Result.addStmt(std::move(*SingleParse)); + } + + return std::move(Result); +} + +Expected<std::unique_ptr<OptionalStmt>> +RCParser::parseSingleOptionalStatement(OptStmtType StmtsType) { + ASSIGN_OR_RETURN(TypeToken, readIdentifier()); + if (TypeToken->equals_lower("CHARACTERISTICS")) + return parseCharacteristicsStmt(); + if (TypeToken->equals_lower("LANGUAGE")) + return parseLanguageStmt(); + if (TypeToken->equals_lower("VERSION")) + return parseVersionStmt(); + + if (StmtsType != OptStmtType::BasicStmt) { + if (TypeToken->equals_lower("CAPTION")) + return parseCaptionStmt(); + if (TypeToken->equals_lower("CLASS")) + return parseClassStmt(); + if (TypeToken->equals_lower("EXSTYLE")) + return parseExStyleStmt(); + if (TypeToken->equals_lower("FONT")) + return parseFontStmt(StmtsType); + if (TypeToken->equals_lower("STYLE")) + return parseStyleStmt(); + } + + return getExpectedError("optional statement type, BEGIN or '{'", + /* IsAlreadyRead = */ true); +} + +RCParser::ParseType RCParser::parseLanguageResource() { + // Read LANGUAGE as an optional statement. If it's read correctly, we can + // upcast it to RCResource. + return parseLanguageStmt(); +} + +RCParser::ParseType RCParser::parseAcceleratorsResource() { + uint16_t MemoryFlags = + parseMemoryFlags(AcceleratorsResource::getDefaultMemoryFlags()); + ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements()); + RETURN_IF_ERROR(consumeType(Kind::BlockBegin)); + + auto Accels = std::make_unique<AcceleratorsResource>( + std::move(*OptStatements), MemoryFlags); + + while (!consumeOptionalType(Kind::BlockEnd)) { + ASSIGN_OR_RETURN(EventResult, readIntOrString()); + RETURN_IF_ERROR(consumeType(Kind::Comma)); + ASSIGN_OR_RETURN(IDResult, readInt()); + ASSIGN_OR_RETURN( + FlagsResult, + parseFlags(AcceleratorsResource::Accelerator::OptionsStr, + AcceleratorsResource::Accelerator::OptionsFlags)); + Accels->addAccelerator(*EventResult, *IDResult, *FlagsResult); + } + + return std::move(Accels); +} + +RCParser::ParseType RCParser::parseCursorResource() { + uint16_t MemoryFlags = + parseMemoryFlags(CursorResource::getDefaultMemoryFlags()); + ASSIGN_OR_RETURN(Arg, readFilename()); + return std::make_unique<CursorResource>(*Arg, MemoryFlags); +} + +RCParser::ParseType RCParser::parseDialogResource(bool IsExtended) { + uint16_t MemoryFlags = + parseMemoryFlags(DialogResource::getDefaultMemoryFlags()); + // Dialog resources have the following format of the arguments: + // DIALOG: x, y, width, height [opt stmts...] {controls...} + // DIALOGEX: x, y, width, height [, helpID] [opt stmts...] {controls...} + // These are very similar, so we parse them together. + ASSIGN_OR_RETURN(LocResult, readIntsWithCommas(4, 4)); + + uint32_t HelpID = 0; // When HelpID is unset, it's assumed to be 0. + if (IsExtended && consumeOptionalType(Kind::Comma)) { + ASSIGN_OR_RETURN(HelpIDResult, readInt()); + HelpID = *HelpIDResult; + } + + ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements( + IsExtended ? OptStmtType::DialogExStmt + : OptStmtType::DialogStmt)); + + assert(isNextTokenKind(Kind::BlockBegin) && + "parseOptionalStatements, when successful, halts on BlockBegin."); + consume(); + + auto Dialog = std::make_unique<DialogResource>( + (*LocResult)[0], (*LocResult)[1], (*LocResult)[2], (*LocResult)[3], + HelpID, std::move(*OptStatements), IsExtended, MemoryFlags); + + while (!consumeOptionalType(Kind::BlockEnd)) { + ASSIGN_OR_RETURN(ControlDefResult, parseControl()); + Dialog->addControl(std::move(*ControlDefResult)); + } + + return std::move(Dialog); +} + +RCParser::ParseType RCParser::parseUserDefinedResource(IntOrString Type) { + uint16_t MemoryFlags = + parseMemoryFlags(UserDefinedResource::getDefaultMemoryFlags()); + if (isEof()) + return getExpectedError("filename, '{' or BEGIN"); + + // Check if this is a file resource. + switch (look().kind()) { + case Kind::String: + case Kind::Identifier: + return std::make_unique<UserDefinedResource>(Type, read().value(), + MemoryFlags); + default: + break; + } + + RETURN_IF_ERROR(consumeType(Kind::BlockBegin)); + std::vector<IntOrString> Data; + + // Consume comma before each consecutive token except the first one. + bool ConsumeComma = false; + while (!consumeOptionalType(Kind::BlockEnd)) { + if (ConsumeComma) + RETURN_IF_ERROR(consumeType(Kind::Comma)); + ConsumeComma = true; + + ASSIGN_OR_RETURN(Item, readIntOrString()); + Data.push_back(*Item); + } + + return std::make_unique<UserDefinedResource>(Type, std::move(Data), + MemoryFlags); +} + +RCParser::ParseType RCParser::parseVersionInfoResource() { + uint16_t MemoryFlags = + parseMemoryFlags(VersionInfoResource::getDefaultMemoryFlags()); + ASSIGN_OR_RETURN(FixedResult, parseVersionInfoFixed()); + ASSIGN_OR_RETURN(BlockResult, parseVersionInfoBlockContents(StringRef())); + return std::make_unique<VersionInfoResource>( + std::move(**BlockResult), std::move(*FixedResult), MemoryFlags); +} + +Expected<Control> RCParser::parseControl() { + // Each control definition (except CONTROL) follows one of the schemes below + // depending on the control class: + // [class] text, id, x, y, width, height [, style] [, exstyle] [, helpID] + // [class] id, x, y, width, height [, style] [, exstyle] [, helpID] + // Note that control ids must be integers. + // Text might be either a string or an integer pointing to resource ID. + ASSIGN_OR_RETURN(ClassResult, readIdentifier()); + std::string ClassUpper = ClassResult->upper(); + auto CtlInfo = Control::SupportedCtls.find(ClassUpper); + if (CtlInfo == Control::SupportedCtls.end()) + return getExpectedError("control type, END or '}'", true); + + // Read caption if necessary. + IntOrString Caption{StringRef()}; + if (CtlInfo->getValue().HasTitle) { + ASSIGN_OR_RETURN(CaptionResult, readIntOrString()); + RETURN_IF_ERROR(consumeType(Kind::Comma)); + Caption = *CaptionResult; + } + + ASSIGN_OR_RETURN(ID, readInt()); + RETURN_IF_ERROR(consumeType(Kind::Comma)); + + IntOrString Class; + Optional<IntWithNotMask> Style; + if (ClassUpper == "CONTROL") { + // CONTROL text, id, class, style, x, y, width, height [, exstyle] [, helpID] + ASSIGN_OR_RETURN(ClassStr, readString()); + RETURN_IF_ERROR(consumeType(Kind::Comma)); + Class = *ClassStr; + ASSIGN_OR_RETURN(StyleVal, parseIntExpr1()); + RETURN_IF_ERROR(consumeType(Kind::Comma)); + Style = *StyleVal; + } else { + Class = CtlInfo->getValue().CtlClass; + } + + // x, y, width, height + ASSIGN_OR_RETURN(Args, readIntsWithCommas(4, 4)); + + if (ClassUpper != "CONTROL") { + if (consumeOptionalType(Kind::Comma)) { + ASSIGN_OR_RETURN(Val, parseIntExpr1()); + Style = *Val; + } + } + + Optional<uint32_t> ExStyle; + if (consumeOptionalType(Kind::Comma)) { + ASSIGN_OR_RETURN(Val, readInt()); + ExStyle = *Val; + } + Optional<uint32_t> HelpID; + if (consumeOptionalType(Kind::Comma)) { + ASSIGN_OR_RETURN(Val, readInt()); + HelpID = *Val; + } + + return Control(*ClassResult, Caption, *ID, (*Args)[0], (*Args)[1], + (*Args)[2], (*Args)[3], Style, ExStyle, HelpID, Class); +} + +RCParser::ParseType RCParser::parseBitmapResource() { + uint16_t MemoryFlags = + parseMemoryFlags(BitmapResource::getDefaultMemoryFlags()); + ASSIGN_OR_RETURN(Arg, readFilename()); + return std::make_unique<BitmapResource>(*Arg, MemoryFlags); +} + +RCParser::ParseType RCParser::parseIconResource() { + uint16_t MemoryFlags = + parseMemoryFlags(IconResource::getDefaultMemoryFlags()); + ASSIGN_OR_RETURN(Arg, readFilename()); + return std::make_unique<IconResource>(*Arg, MemoryFlags); +} + +RCParser::ParseType RCParser::parseHTMLResource() { + uint16_t MemoryFlags = + parseMemoryFlags(HTMLResource::getDefaultMemoryFlags()); + ASSIGN_OR_RETURN(Arg, readFilename()); + return std::make_unique<HTMLResource>(*Arg, MemoryFlags); +} + +RCParser::ParseType RCParser::parseMenuResource() { + uint16_t MemoryFlags = + parseMemoryFlags(MenuResource::getDefaultMemoryFlags()); + ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements()); + ASSIGN_OR_RETURN(Items, parseMenuItemsList()); + return std::make_unique<MenuResource>(std::move(*OptStatements), + std::move(*Items), MemoryFlags); +} + +Expected<MenuDefinitionList> RCParser::parseMenuItemsList() { + RETURN_IF_ERROR(consumeType(Kind::BlockBegin)); + + MenuDefinitionList List; + + // Read a set of items. Each item is of one of three kinds: + // MENUITEM SEPARATOR + // MENUITEM caption:String, result:Int [, menu flags]... + // POPUP caption:String [, menu flags]... { items... } + while (!consumeOptionalType(Kind::BlockEnd)) { + ASSIGN_OR_RETURN(ItemTypeResult, readIdentifier()); + + bool IsMenuItem = ItemTypeResult->equals_lower("MENUITEM"); + bool IsPopup = ItemTypeResult->equals_lower("POPUP"); + if (!IsMenuItem && !IsPopup) + return getExpectedError("MENUITEM, POPUP, END or '}'", true); + + if (IsMenuItem && isNextTokenKind(Kind::Identifier)) { + // Now, expecting SEPARATOR. + ASSIGN_OR_RETURN(SeparatorResult, readIdentifier()); + if (SeparatorResult->equals_lower("SEPARATOR")) { + List.addDefinition(std::make_unique<MenuSeparator>()); + continue; + } + + return getExpectedError("SEPARATOR or string", true); + } + + // Not a separator. Read the caption. + ASSIGN_OR_RETURN(CaptionResult, readString()); + + // If MENUITEM, expect also a comma and an integer. + uint32_t MenuResult = -1; + + if (IsMenuItem) { + RETURN_IF_ERROR(consumeType(Kind::Comma)); + ASSIGN_OR_RETURN(IntResult, readInt()); + MenuResult = *IntResult; + } + + ASSIGN_OR_RETURN(FlagsResult, parseFlags(MenuDefinition::OptionsStr, + MenuDefinition::OptionsFlags)); + + if (IsPopup) { + // If POPUP, read submenu items recursively. + ASSIGN_OR_RETURN(SubMenuResult, parseMenuItemsList()); + List.addDefinition(std::make_unique<PopupItem>( + *CaptionResult, *FlagsResult, std::move(*SubMenuResult))); + continue; + } + + assert(IsMenuItem); + List.addDefinition( + std::make_unique<MenuItem>(*CaptionResult, MenuResult, *FlagsResult)); + } + + return std::move(List); +} + +RCParser::ParseType RCParser::parseStringTableResource() { + uint16_t MemoryFlags = + parseMemoryFlags(StringTableResource::getDefaultMemoryFlags()); + ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements()); + RETURN_IF_ERROR(consumeType(Kind::BlockBegin)); + + auto Table = std::make_unique<StringTableResource>(std::move(*OptStatements), + MemoryFlags); + + // Read strings until we reach the end of the block. + while (!consumeOptionalType(Kind::BlockEnd)) { + // Each definition consists of string's ID (an integer) and a string. + // Some examples in documentation suggest that there might be a comma in + // between, however we strictly adhere to the single statement definition. + ASSIGN_OR_RETURN(IDResult, readInt()); + consumeOptionalType(Kind::Comma); + + std::vector<StringRef> Strings; + ASSIGN_OR_RETURN(StrResult, readString()); + Strings.push_back(*StrResult); + while (isNextTokenKind(Kind::String)) + Strings.push_back(read().value()); + + Table->addStrings(*IDResult, std::move(Strings)); + } + + return std::move(Table); +} + +Expected<std::unique_ptr<VersionInfoBlock>> +RCParser::parseVersionInfoBlockContents(StringRef BlockName) { + RETURN_IF_ERROR(consumeType(Kind::BlockBegin)); + + auto Contents = std::make_unique<VersionInfoBlock>(BlockName); + + while (!isNextTokenKind(Kind::BlockEnd)) { + ASSIGN_OR_RETURN(Stmt, parseVersionInfoStmt()); + Contents->addStmt(std::move(*Stmt)); + } + + consume(); // Consume BlockEnd. + + return std::move(Contents); +} + +Expected<std::unique_ptr<VersionInfoStmt>> RCParser::parseVersionInfoStmt() { + // Expect either BLOCK or VALUE, then a name or a key (a string). + ASSIGN_OR_RETURN(TypeResult, readIdentifier()); + + if (TypeResult->equals_lower("BLOCK")) { + ASSIGN_OR_RETURN(NameResult, readString()); + return parseVersionInfoBlockContents(*NameResult); + } + + if (TypeResult->equals_lower("VALUE")) { + ASSIGN_OR_RETURN(KeyResult, readString()); + // Read a non-empty list of strings and/or ints, each + // possibly preceded by a comma. Unfortunately, the tool behavior depends + // on them existing or not, so we need to memorize where we found them. + std::vector<IntOrString> Values; + std::vector<bool> PrecedingCommas; + RETURN_IF_ERROR(consumeType(Kind::Comma)); + while (!isNextTokenKind(Kind::Identifier) && + !isNextTokenKind(Kind::BlockEnd)) { + // Try to eat a comma if it's not the first statement. + bool HadComma = Values.size() > 0 && consumeOptionalType(Kind::Comma); + ASSIGN_OR_RETURN(ValueResult, readIntOrString()); + Values.push_back(*ValueResult); + PrecedingCommas.push_back(HadComma); + } + return std::make_unique<VersionInfoValue>(*KeyResult, std::move(Values), + std::move(PrecedingCommas)); + } + + return getExpectedError("BLOCK or VALUE", true); +} + +Expected<VersionInfoResource::VersionInfoFixed> +RCParser::parseVersionInfoFixed() { + using RetType = VersionInfoResource::VersionInfoFixed; + RetType Result; + + // Read until the beginning of the block. + while (!isNextTokenKind(Kind::BlockBegin)) { + ASSIGN_OR_RETURN(TypeResult, readIdentifier()); + auto FixedType = RetType::getFixedType(*TypeResult); + + if (!RetType::isTypeSupported(FixedType)) + return getExpectedError("fixed VERSIONINFO statement type", true); + if (Result.IsTypePresent[FixedType]) + return getExpectedError("yet unread fixed VERSIONINFO statement type", + true); + + // VERSION variations take multiple integers. + size_t NumInts = RetType::isVersionType(FixedType) ? 4 : 1; ASSIGN_OR_RETURN(ArgsResult, readIntsWithCommas(1, NumInts)); - SmallVector<uint32_t, 4> ArgInts(ArgsResult->begin(), ArgsResult->end()); + SmallVector<uint32_t, 4> ArgInts(ArgsResult->begin(), ArgsResult->end()); while (ArgInts.size() < NumInts) ArgInts.push_back(0); - Result.setValue(FixedType, ArgInts); - } - - return Result; -} - -RCParser::ParseOptionType RCParser::parseLanguageStmt() { - ASSIGN_OR_RETURN(Args, readIntsWithCommas(/* min = */ 2, /* max = */ 2)); - return std::make_unique<LanguageResource>((*Args)[0], (*Args)[1]); -} - -RCParser::ParseOptionType RCParser::parseCharacteristicsStmt() { - ASSIGN_OR_RETURN(Arg, readInt()); - return std::make_unique<CharacteristicsStmt>(*Arg); -} - -RCParser::ParseOptionType RCParser::parseVersionStmt() { - ASSIGN_OR_RETURN(Arg, readInt()); - return std::make_unique<VersionStmt>(*Arg); -} - -RCParser::ParseOptionType RCParser::parseCaptionStmt() { - ASSIGN_OR_RETURN(Arg, readString()); - return std::make_unique<CaptionStmt>(*Arg); -} - -RCParser::ParseOptionType RCParser::parseClassStmt() { - ASSIGN_OR_RETURN(Arg, readIntOrString()); - return std::make_unique<ClassStmt>(*Arg); -} - -RCParser::ParseOptionType RCParser::parseFontStmt(OptStmtType DialogType) { - assert(DialogType != OptStmtType::BasicStmt); - - ASSIGN_OR_RETURN(SizeResult, readInt()); - RETURN_IF_ERROR(consumeType(Kind::Comma)); - ASSIGN_OR_RETURN(NameResult, readString()); - - // Default values for the optional arguments. - uint32_t FontWeight = 0; - bool FontItalic = false; - uint32_t FontCharset = 1; - if (DialogType == OptStmtType::DialogExStmt) { - if (consumeOptionalType(Kind::Comma)) { - ASSIGN_OR_RETURN(Args, readIntsWithCommas(/* min = */ 0, /* max = */ 3)); - if (Args->size() >= 1) - FontWeight = (*Args)[0]; - if (Args->size() >= 2) - FontItalic = (*Args)[1] != 0; - if (Args->size() >= 3) - FontCharset = (*Args)[2]; - } - } - return std::make_unique<FontStmt>(*SizeResult, *NameResult, FontWeight, - FontItalic, FontCharset); -} - -RCParser::ParseOptionType RCParser::parseStyleStmt() { - ASSIGN_OR_RETURN(Arg, readInt()); - return std::make_unique<StyleStmt>(*Arg); -} - -RCParser::ParseOptionType RCParser::parseExStyleStmt() { - ASSIGN_OR_RETURN(Arg, readInt()); - return std::make_unique<ExStyleStmt>(*Arg); -} - -Error RCParser::getExpectedError(const Twine &Message, bool IsAlreadyRead) { - return make_error<ParserError>( - Message, IsAlreadyRead ? std::prev(CurLoc) : CurLoc, End); -} - -} // namespace rc -} // namespace llvm + Result.setValue(FixedType, ArgInts); + } + + return Result; +} + +RCParser::ParseOptionType RCParser::parseLanguageStmt() { + ASSIGN_OR_RETURN(Args, readIntsWithCommas(/* min = */ 2, /* max = */ 2)); + return std::make_unique<LanguageResource>((*Args)[0], (*Args)[1]); +} + +RCParser::ParseOptionType RCParser::parseCharacteristicsStmt() { + ASSIGN_OR_RETURN(Arg, readInt()); + return std::make_unique<CharacteristicsStmt>(*Arg); +} + +RCParser::ParseOptionType RCParser::parseVersionStmt() { + ASSIGN_OR_RETURN(Arg, readInt()); + return std::make_unique<VersionStmt>(*Arg); +} + +RCParser::ParseOptionType RCParser::parseCaptionStmt() { + ASSIGN_OR_RETURN(Arg, readString()); + return std::make_unique<CaptionStmt>(*Arg); +} + +RCParser::ParseOptionType RCParser::parseClassStmt() { + ASSIGN_OR_RETURN(Arg, readIntOrString()); + return std::make_unique<ClassStmt>(*Arg); +} + +RCParser::ParseOptionType RCParser::parseFontStmt(OptStmtType DialogType) { + assert(DialogType != OptStmtType::BasicStmt); + + ASSIGN_OR_RETURN(SizeResult, readInt()); + RETURN_IF_ERROR(consumeType(Kind::Comma)); + ASSIGN_OR_RETURN(NameResult, readString()); + + // Default values for the optional arguments. + uint32_t FontWeight = 0; + bool FontItalic = false; + uint32_t FontCharset = 1; + if (DialogType == OptStmtType::DialogExStmt) { + if (consumeOptionalType(Kind::Comma)) { + ASSIGN_OR_RETURN(Args, readIntsWithCommas(/* min = */ 0, /* max = */ 3)); + if (Args->size() >= 1) + FontWeight = (*Args)[0]; + if (Args->size() >= 2) + FontItalic = (*Args)[1] != 0; + if (Args->size() >= 3) + FontCharset = (*Args)[2]; + } + } + return std::make_unique<FontStmt>(*SizeResult, *NameResult, FontWeight, + FontItalic, FontCharset); +} + +RCParser::ParseOptionType RCParser::parseStyleStmt() { + ASSIGN_OR_RETURN(Arg, readInt()); + return std::make_unique<StyleStmt>(*Arg); +} + +RCParser::ParseOptionType RCParser::parseExStyleStmt() { + ASSIGN_OR_RETURN(Arg, readInt()); + return std::make_unique<ExStyleStmt>(*Arg); +} + +Error RCParser::getExpectedError(const Twine &Message, bool IsAlreadyRead) { + return make_error<ParserError>( + Message, IsAlreadyRead ? std::prev(CurLoc) : CurLoc, End); +} + +} // namespace rc +} // namespace llvm diff --git a/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptParser.h b/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptParser.h index 4887b0d5db..fe202073b6 100644 --- a/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptParser.h +++ b/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptParser.h @@ -1,192 +1,192 @@ -//===-- ResourceScriptParser.h ----------------------------------*- C++-*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===---------------------------------------------------------------------===// -// -// This defines the RC scripts parser. It takes a sequence of RC tokens -// and then provides the method to parse the resources one by one. -// -//===---------------------------------------------------------------------===// - -#ifndef LLVM_TOOLS_LLVMRC_RESOURCESCRIPTPARSER_H -#define LLVM_TOOLS_LLVMRC_RESOURCESCRIPTPARSER_H - -#include "ResourceScriptStmt.h" -#include "ResourceScriptToken.h" - -#include "llvm/Support/Compiler.h" -#include "llvm/Support/raw_ostream.h" - -#include <system_error> -#include <vector> - -namespace llvm { -namespace opt { -class InputArgList; -} -namespace rc { - -class RCParser { -public: - using LocIter = std::vector<RCToken>::iterator; - using ParseType = Expected<std::unique_ptr<RCResource>>; - using ParseOptionType = Expected<std::unique_ptr<OptionalStmt>>; - - // Class describing a single failure of parser. - class ParserError : public ErrorInfo<ParserError> { - public: - ParserError(const Twine &Expected, const LocIter CurLoc, const LocIter End); - - void log(raw_ostream &OS) const override { OS << CurMessage; } - std::error_code convertToErrorCode() const override { - return std::make_error_code(std::errc::invalid_argument); - } - const std::string &getMessage() const { return CurMessage; } - - static char ID; // Keep llvm::Error happy. - - private: - std::string CurMessage; - LocIter ErrorLoc, FileEnd; - }; - - explicit RCParser(std::vector<RCToken> TokenList); - - // Reads and returns a single resource definition, or error message if any - // occurred. - ParseType parseSingleResource(); - - bool isEof() const; - -private: - using Kind = RCToken::Kind; - - // Checks if the current parser state points to the token of type TokenKind. - bool isNextTokenKind(Kind TokenKind) const; - - // These methods assume that the parser is not in EOF state. - - // Take a look at the current token. Do not fetch it. - const RCToken &look() const; - // Read the current token and advance the state by one token. - const RCToken &read(); - // Advance the state by one token, discarding the current token. - void consume(); - - // The following methods try to read a single token, check if it has the - // correct type and then parse it. - // Each integer can be written as an arithmetic expression producing an - // unsigned 32-bit integer. - Expected<RCInt> readInt(); // Parse an integer. - Expected<StringRef> readString(); // Parse a string. - Expected<StringRef> readIdentifier(); // Parse an identifier. - Expected<StringRef> readFilename(); // Parse a filename. - Expected<IntOrString> readIntOrString(); // Parse an integer or a string. - Expected<IntOrString> readTypeOrName(); // Parse an integer or an identifier. - - // Helper integer expression parsing methods. - Expected<IntWithNotMask> parseIntExpr1(); - Expected<IntWithNotMask> parseIntExpr2(); - - // Advance the state by one, discarding the current token. - // If the discarded token had an incorrect type, fail. - Error consumeType(Kind TokenKind); - - // Check the current token type. If it's TokenKind, discard it. - // Return true if the parser consumed this token successfully. - bool consumeOptionalType(Kind TokenKind); - - // Read at least MinCount, and at most MaxCount integers separated by - // commas. The parser stops reading after fetching MaxCount integers - // or after an error occurs. Whenever the parser reads a comma, it - // expects an integer to follow. - Expected<SmallVector<RCInt, 8>> readIntsWithCommas(size_t MinCount, - size_t MaxCount); - - // Read an unknown number of flags preceded by commas. Each correct flag - // has an entry in FlagDesc array of length NumFlags. In case i-th - // flag (0-based) has been read, the result is OR-ed with FlagValues[i]. - // As long as parser has a comma to read, it expects to be fed with - // a correct flag afterwards. - Expected<uint32_t> parseFlags(ArrayRef<StringRef> FlagDesc, - ArrayRef<uint32_t> FlagValues); - - // Reads a set of optional statements. These can change the behavior of - // a number of resource types (e.g. STRINGTABLE, MENU or DIALOG) if provided - // before the main block with the contents of the resource. - // Usually, resources use a basic set of optional statements: - // CHARACTERISTICS, LANGUAGE, VERSION - // However, DIALOG and DIALOGEX extend this list by the following items: - // CAPTION, CLASS, EXSTYLE, FONT, MENU, STYLE - // UseExtendedStatements flag (off by default) allows the parser to read - // the additional types of statements. - // - // Ref (to the list of all optional statements): - // msdn.microsoft.com/en-us/library/windows/desktop/aa381002(v=vs.85).aspx - enum class OptStmtType { BasicStmt, DialogStmt, DialogExStmt }; - - uint16_t parseMemoryFlags(uint16_t DefaultFlags); - - Expected<OptionalStmtList> - parseOptionalStatements(OptStmtType StmtsType = OptStmtType::BasicStmt); - - // Read a single optional statement. - Expected<std::unique_ptr<OptionalStmt>> - parseSingleOptionalStatement(OptStmtType StmtsType = OptStmtType::BasicStmt); - - // Top-level resource parsers. - ParseType parseLanguageResource(); - ParseType parseAcceleratorsResource(); - ParseType parseBitmapResource(); - ParseType parseCursorResource(); - ParseType parseDialogResource(bool IsExtended); - ParseType parseIconResource(); - ParseType parseHTMLResource(); - ParseType parseMenuResource(); - ParseType parseStringTableResource(); - ParseType parseUserDefinedResource(IntOrString Type); - ParseType parseVersionInfoResource(); - - // Helper DIALOG parser - a single control. - Expected<Control> parseControl(); - - // Helper MENU parser. - Expected<MenuDefinitionList> parseMenuItemsList(); - - // Helper VERSIONINFO parser - read the contents of a single BLOCK statement, - // from BEGIN to END. - Expected<std::unique_ptr<VersionInfoBlock>> - parseVersionInfoBlockContents(StringRef BlockName); - // Helper VERSIONINFO parser - read either VALUE or BLOCK statement. - Expected<std::unique_ptr<VersionInfoStmt>> parseVersionInfoStmt(); - // Helper VERSIONINFO parser - read fixed VERSIONINFO statements. - Expected<VersionInfoResource::VersionInfoFixed> parseVersionInfoFixed(); - - // Optional statement parsers. - ParseOptionType parseLanguageStmt(); - ParseOptionType parseCharacteristicsStmt(); - ParseOptionType parseVersionStmt(); - ParseOptionType parseCaptionStmt(); - ParseOptionType parseClassStmt(); - ParseOptionType parseExStyleStmt(); - ParseOptionType parseFontStmt(OptStmtType DialogType); - ParseOptionType parseStyleStmt(); - - // Raises an error. If IsAlreadyRead = false (default), this complains about - // the token that couldn't be parsed. If the flag is on, this complains about - // the correctly read token that makes no sense (that is, the current parser - // state is beyond the erroneous token.) - Error getExpectedError(const Twine &Message, bool IsAlreadyRead = false); - - std::vector<RCToken> Tokens; - LocIter CurLoc; - const LocIter End; -}; - -} // namespace rc -} // namespace llvm - -#endif +//===-- ResourceScriptParser.h ----------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// +// +// This defines the RC scripts parser. It takes a sequence of RC tokens +// and then provides the method to parse the resources one by one. +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_TOOLS_LLVMRC_RESOURCESCRIPTPARSER_H +#define LLVM_TOOLS_LLVMRC_RESOURCESCRIPTPARSER_H + +#include "ResourceScriptStmt.h" +#include "ResourceScriptToken.h" + +#include "llvm/Support/Compiler.h" +#include "llvm/Support/raw_ostream.h" + +#include <system_error> +#include <vector> + +namespace llvm { +namespace opt { +class InputArgList; +} +namespace rc { + +class RCParser { +public: + using LocIter = std::vector<RCToken>::iterator; + using ParseType = Expected<std::unique_ptr<RCResource>>; + using ParseOptionType = Expected<std::unique_ptr<OptionalStmt>>; + + // Class describing a single failure of parser. + class ParserError : public ErrorInfo<ParserError> { + public: + ParserError(const Twine &Expected, const LocIter CurLoc, const LocIter End); + + void log(raw_ostream &OS) const override { OS << CurMessage; } + std::error_code convertToErrorCode() const override { + return std::make_error_code(std::errc::invalid_argument); + } + const std::string &getMessage() const { return CurMessage; } + + static char ID; // Keep llvm::Error happy. + + private: + std::string CurMessage; + LocIter ErrorLoc, FileEnd; + }; + + explicit RCParser(std::vector<RCToken> TokenList); + + // Reads and returns a single resource definition, or error message if any + // occurred. + ParseType parseSingleResource(); + + bool isEof() const; + +private: + using Kind = RCToken::Kind; + + // Checks if the current parser state points to the token of type TokenKind. + bool isNextTokenKind(Kind TokenKind) const; + + // These methods assume that the parser is not in EOF state. + + // Take a look at the current token. Do not fetch it. + const RCToken &look() const; + // Read the current token and advance the state by one token. + const RCToken &read(); + // Advance the state by one token, discarding the current token. + void consume(); + + // The following methods try to read a single token, check if it has the + // correct type and then parse it. + // Each integer can be written as an arithmetic expression producing an + // unsigned 32-bit integer. + Expected<RCInt> readInt(); // Parse an integer. + Expected<StringRef> readString(); // Parse a string. + Expected<StringRef> readIdentifier(); // Parse an identifier. + Expected<StringRef> readFilename(); // Parse a filename. + Expected<IntOrString> readIntOrString(); // Parse an integer or a string. + Expected<IntOrString> readTypeOrName(); // Parse an integer or an identifier. + + // Helper integer expression parsing methods. + Expected<IntWithNotMask> parseIntExpr1(); + Expected<IntWithNotMask> parseIntExpr2(); + + // Advance the state by one, discarding the current token. + // If the discarded token had an incorrect type, fail. + Error consumeType(Kind TokenKind); + + // Check the current token type. If it's TokenKind, discard it. + // Return true if the parser consumed this token successfully. + bool consumeOptionalType(Kind TokenKind); + + // Read at least MinCount, and at most MaxCount integers separated by + // commas. The parser stops reading after fetching MaxCount integers + // or after an error occurs. Whenever the parser reads a comma, it + // expects an integer to follow. + Expected<SmallVector<RCInt, 8>> readIntsWithCommas(size_t MinCount, + size_t MaxCount); + + // Read an unknown number of flags preceded by commas. Each correct flag + // has an entry in FlagDesc array of length NumFlags. In case i-th + // flag (0-based) has been read, the result is OR-ed with FlagValues[i]. + // As long as parser has a comma to read, it expects to be fed with + // a correct flag afterwards. + Expected<uint32_t> parseFlags(ArrayRef<StringRef> FlagDesc, + ArrayRef<uint32_t> FlagValues); + + // Reads a set of optional statements. These can change the behavior of + // a number of resource types (e.g. STRINGTABLE, MENU or DIALOG) if provided + // before the main block with the contents of the resource. + // Usually, resources use a basic set of optional statements: + // CHARACTERISTICS, LANGUAGE, VERSION + // However, DIALOG and DIALOGEX extend this list by the following items: + // CAPTION, CLASS, EXSTYLE, FONT, MENU, STYLE + // UseExtendedStatements flag (off by default) allows the parser to read + // the additional types of statements. + // + // Ref (to the list of all optional statements): + // msdn.microsoft.com/en-us/library/windows/desktop/aa381002(v=vs.85).aspx + enum class OptStmtType { BasicStmt, DialogStmt, DialogExStmt }; + + uint16_t parseMemoryFlags(uint16_t DefaultFlags); + + Expected<OptionalStmtList> + parseOptionalStatements(OptStmtType StmtsType = OptStmtType::BasicStmt); + + // Read a single optional statement. + Expected<std::unique_ptr<OptionalStmt>> + parseSingleOptionalStatement(OptStmtType StmtsType = OptStmtType::BasicStmt); + + // Top-level resource parsers. + ParseType parseLanguageResource(); + ParseType parseAcceleratorsResource(); + ParseType parseBitmapResource(); + ParseType parseCursorResource(); + ParseType parseDialogResource(bool IsExtended); + ParseType parseIconResource(); + ParseType parseHTMLResource(); + ParseType parseMenuResource(); + ParseType parseStringTableResource(); + ParseType parseUserDefinedResource(IntOrString Type); + ParseType parseVersionInfoResource(); + + // Helper DIALOG parser - a single control. + Expected<Control> parseControl(); + + // Helper MENU parser. + Expected<MenuDefinitionList> parseMenuItemsList(); + + // Helper VERSIONINFO parser - read the contents of a single BLOCK statement, + // from BEGIN to END. + Expected<std::unique_ptr<VersionInfoBlock>> + parseVersionInfoBlockContents(StringRef BlockName); + // Helper VERSIONINFO parser - read either VALUE or BLOCK statement. + Expected<std::unique_ptr<VersionInfoStmt>> parseVersionInfoStmt(); + // Helper VERSIONINFO parser - read fixed VERSIONINFO statements. + Expected<VersionInfoResource::VersionInfoFixed> parseVersionInfoFixed(); + + // Optional statement parsers. + ParseOptionType parseLanguageStmt(); + ParseOptionType parseCharacteristicsStmt(); + ParseOptionType parseVersionStmt(); + ParseOptionType parseCaptionStmt(); + ParseOptionType parseClassStmt(); + ParseOptionType parseExStyleStmt(); + ParseOptionType parseFontStmt(OptStmtType DialogType); + ParseOptionType parseStyleStmt(); + + // Raises an error. If IsAlreadyRead = false (default), this complains about + // the token that couldn't be parsed. If the flag is on, this complains about + // the correctly read token that makes no sense (that is, the current parser + // state is beyond the erroneous token.) + Error getExpectedError(const Twine &Message, bool IsAlreadyRead = false); + + std::vector<RCToken> Tokens; + LocIter CurLoc; + const LocIter End; +}; + +} // namespace rc +} // namespace llvm + +#endif diff --git a/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptStmt.cpp b/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptStmt.cpp index eaf9711af6..ef8c345418 100644 --- a/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptStmt.cpp +++ b/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptStmt.cpp @@ -1,294 +1,294 @@ -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===---------------------------------------------------------------------===// -// -// This implements methods defined in ResourceScriptStmt.h. -// -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380599(v=vs.85).aspx -// -//===---------------------------------------------------------------------===// - -#include "ResourceScriptStmt.h" - -namespace llvm { -namespace rc { - -raw_ostream &operator<<(raw_ostream &OS, const IntOrString &Item) { - if (Item.IsInt) - return OS << Item.Data.Int; - else - return OS << Item.Data.String; -} - -raw_ostream &OptionalStmtList::log(raw_ostream &OS) const { - for (const auto &Stmt : Statements) { - OS << " Option: "; - Stmt->log(OS); - } - return OS; -} - -raw_ostream &LanguageResource::log(raw_ostream &OS) const { - return OS << "Language: " << Lang << ", Sublanguage: " << SubLang << "\n"; -} - -StringRef AcceleratorsResource::Accelerator::OptionsStr - [AcceleratorsResource::Accelerator::NumFlags] = { - "ASCII", "VIRTKEY", "NOINVERT", "ALT", "SHIFT", "CONTROL"}; - -uint32_t AcceleratorsResource::Accelerator::OptionsFlags - [AcceleratorsResource::Accelerator::NumFlags] = {ASCII, VIRTKEY, NOINVERT, - ALT, SHIFT, CONTROL}; - -raw_ostream &AcceleratorsResource::log(raw_ostream &OS) const { - OS << "Accelerators (" << ResName << "): \n"; - OptStatements->log(OS); - for (const auto &Acc : Accelerators) { - OS << " Accelerator: " << Acc.Event << " " << Acc.Id; - for (size_t i = 0; i < Accelerator::NumFlags; ++i) - if (Acc.Flags & Accelerator::OptionsFlags[i]) - OS << " " << Accelerator::OptionsStr[i]; - OS << "\n"; - } - return OS; -} - -raw_ostream &BitmapResource::log(raw_ostream &OS) const { - return OS << "Bitmap (" << ResName << "): " << BitmapLoc << "\n"; -} - -raw_ostream &CursorResource::log(raw_ostream &OS) const { - return OS << "Cursor (" << ResName << "): " << CursorLoc << "\n"; -} - -raw_ostream &IconResource::log(raw_ostream &OS) const { - return OS << "Icon (" << ResName << "): " << IconLoc << "\n"; -} - -raw_ostream &HTMLResource::log(raw_ostream &OS) const { - return OS << "HTML (" << ResName << "): " << HTMLLoc << "\n"; -} - -StringRef MenuDefinition::OptionsStr[MenuDefinition::NumFlags] = { - "CHECKED", "GRAYED", "HELP", "INACTIVE", "MENUBARBREAK", "MENUBREAK"}; - -uint32_t MenuDefinition::OptionsFlags[MenuDefinition::NumFlags] = { - CHECKED, GRAYED, HELP, INACTIVE, MENUBARBREAK, MENUBREAK}; - -raw_ostream &MenuDefinition::logFlags(raw_ostream &OS, uint16_t Flags) { - for (size_t i = 0; i < NumFlags; ++i) - if (Flags & OptionsFlags[i]) - OS << " " << OptionsStr[i]; - return OS; -} - -raw_ostream &MenuDefinitionList::log(raw_ostream &OS) const { - OS << " Menu list starts\n"; - for (auto &Item : Definitions) - Item->log(OS); - return OS << " Menu list ends\n"; -} - -raw_ostream &MenuItem::log(raw_ostream &OS) const { - OS << " MenuItem (" << Name << "), ID = " << Id; - logFlags(OS, Flags); - return OS << "\n"; -} - -raw_ostream &MenuSeparator::log(raw_ostream &OS) const { - return OS << " Menu separator\n"; -} - -raw_ostream &PopupItem::log(raw_ostream &OS) const { - OS << " Popup (" << Name << ")"; - logFlags(OS, Flags); - OS << ":\n"; - return SubItems.log(OS); -} - -raw_ostream &MenuResource::log(raw_ostream &OS) const { - OS << "Menu (" << ResName << "):\n"; - OptStatements->log(OS); - return Elements.log(OS); -} - -raw_ostream &StringTableResource::log(raw_ostream &OS) const { - OS << "StringTable:\n"; - OptStatements->log(OS); - for (const auto &String : Table) { - OS << " " << String.first << " =>"; - for (const auto &S : String.second) - OS << " " << S; - OS << "\n"; - } - return OS; -} - -const StringMap<Control::CtlInfo> Control::SupportedCtls = { - {"LTEXT", CtlInfo{0x50020000, ClsStatic, true}}, - {"CTEXT", CtlInfo{0x50020001, ClsStatic, true}}, - {"RTEXT", CtlInfo{0x50020002, ClsStatic, true}}, - {"ICON", CtlInfo{0x50000003, ClsStatic, true}}, - {"PUSHBUTTON", CtlInfo{0x50010000, ClsButton, true}}, - {"DEFPUSHBUTTON", CtlInfo{0x50010001, ClsButton, true}}, - {"AUTO3STATE", CtlInfo{0x50010006, ClsButton, true}}, - {"AUTOCHECKBOX", CtlInfo{0x50010003, ClsButton, true}}, - {"AUTORADIOBUTTON", CtlInfo{0x50000009, ClsButton, true}}, - {"CHECKBOX", CtlInfo{0x50010002, ClsButton, true}}, - {"GROUPBOX", CtlInfo{0x50000007, ClsButton, true}}, - {"RADIOBUTTON", CtlInfo{0x50000004, ClsButton, true}}, - {"STATE3", CtlInfo{0x50010005, ClsButton, true}}, - {"PUSHBOX", CtlInfo{0x5001000A, ClsButton, true}}, - {"EDITTEXT", CtlInfo{0x50810000, ClsEdit, false}}, - {"COMBOBOX", CtlInfo{0x50000000, ClsComboBox, false}}, - {"LISTBOX", CtlInfo{0x50800001, ClsListBox, false}}, - {"SCROLLBAR", CtlInfo{0x50000000, ClsScrollBar, false}}, - {"CONTROL", CtlInfo{0x50000000, 0, true}}, -}; - -raw_ostream &Control::log(raw_ostream &OS) const { - OS << " Control (" << ID << "): " << Type << ", title: " << Title - << ", loc: (" << X << ", " << Y << "), size: [" << Width << ", " << Height - << "]"; - if (Style) - OS << ", style: " << (*Style).getValue(); - if (ExtStyle) - OS << ", ext. style: " << *ExtStyle; - if (HelpID) - OS << ", help ID: " << *HelpID; - return OS << "\n"; -} - -raw_ostream &DialogResource::log(raw_ostream &OS) const { - OS << "Dialog" << (IsExtended ? "Ex" : "") << " (" << ResName << "): loc: (" - << X << ", " << Y << "), size: [" << Width << ", " << Height - << "], help ID: " << HelpID << "\n"; - OptStatements->log(OS); - for (auto &Ctl : Controls) - Ctl.log(OS); - return OS; -} - -raw_ostream &VersionInfoBlock::log(raw_ostream &OS) const { - OS << " Start of block (name: " << Name << ")\n"; - for (auto &Stmt : Stmts) - Stmt->log(OS); - return OS << " End of block\n"; -} - -raw_ostream &VersionInfoValue::log(raw_ostream &OS) const { - OS << " " << Key << " =>"; - size_t NumValues = Values.size(); - for (size_t Id = 0; Id < NumValues; ++Id) { - if (Id > 0 && HasPrecedingComma[Id]) - OS << ","; - OS << " " << Values[Id]; - } - return OS << "\n"; -} - -using VersionInfoFixed = VersionInfoResource::VersionInfoFixed; -using VersionInfoFixedType = VersionInfoFixed::VersionInfoFixedType; - -const StringRef - VersionInfoFixed::FixedFieldsNames[VersionInfoFixed::FtNumTypes] = { - "", "FILEVERSION", "PRODUCTVERSION", "FILEFLAGSMASK", - "FILEFLAGS", "FILEOS", "FILETYPE", "FILESUBTYPE"}; - -const StringMap<VersionInfoFixedType> VersionInfoFixed::FixedFieldsInfoMap = { - {FixedFieldsNames[FtFileVersion], FtFileVersion}, - {FixedFieldsNames[FtProductVersion], FtProductVersion}, - {FixedFieldsNames[FtFileFlagsMask], FtFileFlagsMask}, - {FixedFieldsNames[FtFileFlags], FtFileFlags}, - {FixedFieldsNames[FtFileOS], FtFileOS}, - {FixedFieldsNames[FtFileType], FtFileType}, - {FixedFieldsNames[FtFileSubtype], FtFileSubtype}}; - -VersionInfoFixedType VersionInfoFixed::getFixedType(StringRef Type) { - auto UpperType = Type.upper(); - auto Iter = FixedFieldsInfoMap.find(UpperType); - if (Iter != FixedFieldsInfoMap.end()) - return Iter->getValue(); - return FtUnknown; -} - -bool VersionInfoFixed::isTypeSupported(VersionInfoFixedType Type) { - return FtUnknown < Type && Type < FtNumTypes; -} - -bool VersionInfoFixed::isVersionType(VersionInfoFixedType Type) { - switch (Type) { - case FtFileVersion: - case FtProductVersion: - return true; - - default: - return false; - } -} - -raw_ostream &VersionInfoFixed::log(raw_ostream &OS) const { - for (int Type = FtUnknown; Type < FtNumTypes; ++Type) { - if (!isTypeSupported((VersionInfoFixedType)Type)) - continue; - OS << " Fixed: " << FixedFieldsNames[Type] << ":"; - for (uint32_t Val : FixedInfo[Type]) - OS << " " << Val; - OS << "\n"; - } - return OS; -} - -raw_ostream &VersionInfoResource::log(raw_ostream &OS) const { - OS << "VersionInfo (" << ResName << "):\n"; - FixedData.log(OS); - return MainBlock.log(OS); -} - -raw_ostream &UserDefinedResource::log(raw_ostream &OS) const { - OS << "User-defined (type: " << Type << ", name: " << ResName << "): "; - if (IsFileResource) - return OS << FileLoc << "\n"; - OS << "data = "; - for (auto &Item : Contents) - OS << Item << " "; - return OS << "\n"; -} - -raw_ostream &CharacteristicsStmt::log(raw_ostream &OS) const { - return OS << "Characteristics: " << Value << "\n"; -} - -raw_ostream &VersionStmt::log(raw_ostream &OS) const { - return OS << "Version: " << Value << "\n"; -} - -raw_ostream &CaptionStmt::log(raw_ostream &OS) const { - return OS << "Caption: " << Value << "\n"; -} - -raw_ostream &ClassStmt::log(raw_ostream &OS) const { - return OS << "Class: " << Value << "\n"; -} - -raw_ostream &FontStmt::log(raw_ostream &OS) const { - OS << "Font: size = " << Size << ", face = " << Name - << ", weight = " << Weight; - if (Italic) - OS << ", italic"; - return OS << ", charset = " << Charset << "\n"; -} - -raw_ostream &StyleStmt::log(raw_ostream &OS) const { - return OS << "Style: " << Value << "\n"; -} - -raw_ostream &ExStyleStmt::log(raw_ostream &OS) const { - return OS << "ExStyle: " << Value << "\n"; -} - -} // namespace rc -} // namespace llvm +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// +// +// This implements methods defined in ResourceScriptStmt.h. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380599(v=vs.85).aspx +// +//===---------------------------------------------------------------------===// + +#include "ResourceScriptStmt.h" + +namespace llvm { +namespace rc { + +raw_ostream &operator<<(raw_ostream &OS, const IntOrString &Item) { + if (Item.IsInt) + return OS << Item.Data.Int; + else + return OS << Item.Data.String; +} + +raw_ostream &OptionalStmtList::log(raw_ostream &OS) const { + for (const auto &Stmt : Statements) { + OS << " Option: "; + Stmt->log(OS); + } + return OS; +} + +raw_ostream &LanguageResource::log(raw_ostream &OS) const { + return OS << "Language: " << Lang << ", Sublanguage: " << SubLang << "\n"; +} + +StringRef AcceleratorsResource::Accelerator::OptionsStr + [AcceleratorsResource::Accelerator::NumFlags] = { + "ASCII", "VIRTKEY", "NOINVERT", "ALT", "SHIFT", "CONTROL"}; + +uint32_t AcceleratorsResource::Accelerator::OptionsFlags + [AcceleratorsResource::Accelerator::NumFlags] = {ASCII, VIRTKEY, NOINVERT, + ALT, SHIFT, CONTROL}; + +raw_ostream &AcceleratorsResource::log(raw_ostream &OS) const { + OS << "Accelerators (" << ResName << "): \n"; + OptStatements->log(OS); + for (const auto &Acc : Accelerators) { + OS << " Accelerator: " << Acc.Event << " " << Acc.Id; + for (size_t i = 0; i < Accelerator::NumFlags; ++i) + if (Acc.Flags & Accelerator::OptionsFlags[i]) + OS << " " << Accelerator::OptionsStr[i]; + OS << "\n"; + } + return OS; +} + +raw_ostream &BitmapResource::log(raw_ostream &OS) const { + return OS << "Bitmap (" << ResName << "): " << BitmapLoc << "\n"; +} + +raw_ostream &CursorResource::log(raw_ostream &OS) const { + return OS << "Cursor (" << ResName << "): " << CursorLoc << "\n"; +} + +raw_ostream &IconResource::log(raw_ostream &OS) const { + return OS << "Icon (" << ResName << "): " << IconLoc << "\n"; +} + +raw_ostream &HTMLResource::log(raw_ostream &OS) const { + return OS << "HTML (" << ResName << "): " << HTMLLoc << "\n"; +} + +StringRef MenuDefinition::OptionsStr[MenuDefinition::NumFlags] = { + "CHECKED", "GRAYED", "HELP", "INACTIVE", "MENUBARBREAK", "MENUBREAK"}; + +uint32_t MenuDefinition::OptionsFlags[MenuDefinition::NumFlags] = { + CHECKED, GRAYED, HELP, INACTIVE, MENUBARBREAK, MENUBREAK}; + +raw_ostream &MenuDefinition::logFlags(raw_ostream &OS, uint16_t Flags) { + for (size_t i = 0; i < NumFlags; ++i) + if (Flags & OptionsFlags[i]) + OS << " " << OptionsStr[i]; + return OS; +} + +raw_ostream &MenuDefinitionList::log(raw_ostream &OS) const { + OS << " Menu list starts\n"; + for (auto &Item : Definitions) + Item->log(OS); + return OS << " Menu list ends\n"; +} + +raw_ostream &MenuItem::log(raw_ostream &OS) const { + OS << " MenuItem (" << Name << "), ID = " << Id; + logFlags(OS, Flags); + return OS << "\n"; +} + +raw_ostream &MenuSeparator::log(raw_ostream &OS) const { + return OS << " Menu separator\n"; +} + +raw_ostream &PopupItem::log(raw_ostream &OS) const { + OS << " Popup (" << Name << ")"; + logFlags(OS, Flags); + OS << ":\n"; + return SubItems.log(OS); +} + +raw_ostream &MenuResource::log(raw_ostream &OS) const { + OS << "Menu (" << ResName << "):\n"; + OptStatements->log(OS); + return Elements.log(OS); +} + +raw_ostream &StringTableResource::log(raw_ostream &OS) const { + OS << "StringTable:\n"; + OptStatements->log(OS); + for (const auto &String : Table) { + OS << " " << String.first << " =>"; + for (const auto &S : String.second) + OS << " " << S; + OS << "\n"; + } + return OS; +} + +const StringMap<Control::CtlInfo> Control::SupportedCtls = { + {"LTEXT", CtlInfo{0x50020000, ClsStatic, true}}, + {"CTEXT", CtlInfo{0x50020001, ClsStatic, true}}, + {"RTEXT", CtlInfo{0x50020002, ClsStatic, true}}, + {"ICON", CtlInfo{0x50000003, ClsStatic, true}}, + {"PUSHBUTTON", CtlInfo{0x50010000, ClsButton, true}}, + {"DEFPUSHBUTTON", CtlInfo{0x50010001, ClsButton, true}}, + {"AUTO3STATE", CtlInfo{0x50010006, ClsButton, true}}, + {"AUTOCHECKBOX", CtlInfo{0x50010003, ClsButton, true}}, + {"AUTORADIOBUTTON", CtlInfo{0x50000009, ClsButton, true}}, + {"CHECKBOX", CtlInfo{0x50010002, ClsButton, true}}, + {"GROUPBOX", CtlInfo{0x50000007, ClsButton, true}}, + {"RADIOBUTTON", CtlInfo{0x50000004, ClsButton, true}}, + {"STATE3", CtlInfo{0x50010005, ClsButton, true}}, + {"PUSHBOX", CtlInfo{0x5001000A, ClsButton, true}}, + {"EDITTEXT", CtlInfo{0x50810000, ClsEdit, false}}, + {"COMBOBOX", CtlInfo{0x50000000, ClsComboBox, false}}, + {"LISTBOX", CtlInfo{0x50800001, ClsListBox, false}}, + {"SCROLLBAR", CtlInfo{0x50000000, ClsScrollBar, false}}, + {"CONTROL", CtlInfo{0x50000000, 0, true}}, +}; + +raw_ostream &Control::log(raw_ostream &OS) const { + OS << " Control (" << ID << "): " << Type << ", title: " << Title + << ", loc: (" << X << ", " << Y << "), size: [" << Width << ", " << Height + << "]"; + if (Style) + OS << ", style: " << (*Style).getValue(); + if (ExtStyle) + OS << ", ext. style: " << *ExtStyle; + if (HelpID) + OS << ", help ID: " << *HelpID; + return OS << "\n"; +} + +raw_ostream &DialogResource::log(raw_ostream &OS) const { + OS << "Dialog" << (IsExtended ? "Ex" : "") << " (" << ResName << "): loc: (" + << X << ", " << Y << "), size: [" << Width << ", " << Height + << "], help ID: " << HelpID << "\n"; + OptStatements->log(OS); + for (auto &Ctl : Controls) + Ctl.log(OS); + return OS; +} + +raw_ostream &VersionInfoBlock::log(raw_ostream &OS) const { + OS << " Start of block (name: " << Name << ")\n"; + for (auto &Stmt : Stmts) + Stmt->log(OS); + return OS << " End of block\n"; +} + +raw_ostream &VersionInfoValue::log(raw_ostream &OS) const { + OS << " " << Key << " =>"; + size_t NumValues = Values.size(); + for (size_t Id = 0; Id < NumValues; ++Id) { + if (Id > 0 && HasPrecedingComma[Id]) + OS << ","; + OS << " " << Values[Id]; + } + return OS << "\n"; +} + +using VersionInfoFixed = VersionInfoResource::VersionInfoFixed; +using VersionInfoFixedType = VersionInfoFixed::VersionInfoFixedType; + +const StringRef + VersionInfoFixed::FixedFieldsNames[VersionInfoFixed::FtNumTypes] = { + "", "FILEVERSION", "PRODUCTVERSION", "FILEFLAGSMASK", + "FILEFLAGS", "FILEOS", "FILETYPE", "FILESUBTYPE"}; + +const StringMap<VersionInfoFixedType> VersionInfoFixed::FixedFieldsInfoMap = { + {FixedFieldsNames[FtFileVersion], FtFileVersion}, + {FixedFieldsNames[FtProductVersion], FtProductVersion}, + {FixedFieldsNames[FtFileFlagsMask], FtFileFlagsMask}, + {FixedFieldsNames[FtFileFlags], FtFileFlags}, + {FixedFieldsNames[FtFileOS], FtFileOS}, + {FixedFieldsNames[FtFileType], FtFileType}, + {FixedFieldsNames[FtFileSubtype], FtFileSubtype}}; + +VersionInfoFixedType VersionInfoFixed::getFixedType(StringRef Type) { + auto UpperType = Type.upper(); + auto Iter = FixedFieldsInfoMap.find(UpperType); + if (Iter != FixedFieldsInfoMap.end()) + return Iter->getValue(); + return FtUnknown; +} + +bool VersionInfoFixed::isTypeSupported(VersionInfoFixedType Type) { + return FtUnknown < Type && Type < FtNumTypes; +} + +bool VersionInfoFixed::isVersionType(VersionInfoFixedType Type) { + switch (Type) { + case FtFileVersion: + case FtProductVersion: + return true; + + default: + return false; + } +} + +raw_ostream &VersionInfoFixed::log(raw_ostream &OS) const { + for (int Type = FtUnknown; Type < FtNumTypes; ++Type) { + if (!isTypeSupported((VersionInfoFixedType)Type)) + continue; + OS << " Fixed: " << FixedFieldsNames[Type] << ":"; + for (uint32_t Val : FixedInfo[Type]) + OS << " " << Val; + OS << "\n"; + } + return OS; +} + +raw_ostream &VersionInfoResource::log(raw_ostream &OS) const { + OS << "VersionInfo (" << ResName << "):\n"; + FixedData.log(OS); + return MainBlock.log(OS); +} + +raw_ostream &UserDefinedResource::log(raw_ostream &OS) const { + OS << "User-defined (type: " << Type << ", name: " << ResName << "): "; + if (IsFileResource) + return OS << FileLoc << "\n"; + OS << "data = "; + for (auto &Item : Contents) + OS << Item << " "; + return OS << "\n"; +} + +raw_ostream &CharacteristicsStmt::log(raw_ostream &OS) const { + return OS << "Characteristics: " << Value << "\n"; +} + +raw_ostream &VersionStmt::log(raw_ostream &OS) const { + return OS << "Version: " << Value << "\n"; +} + +raw_ostream &CaptionStmt::log(raw_ostream &OS) const { + return OS << "Caption: " << Value << "\n"; +} + +raw_ostream &ClassStmt::log(raw_ostream &OS) const { + return OS << "Class: " << Value << "\n"; +} + +raw_ostream &FontStmt::log(raw_ostream &OS) const { + OS << "Font: size = " << Size << ", face = " << Name + << ", weight = " << Weight; + if (Italic) + OS << ", italic"; + return OS << ", charset = " << Charset << "\n"; +} + +raw_ostream &StyleStmt::log(raw_ostream &OS) const { + return OS << "Style: " << Value << "\n"; +} + +raw_ostream &ExStyleStmt::log(raw_ostream &OS) const { + return OS << "ExStyle: " << Value << "\n"; +} + +} // namespace rc +} // namespace llvm diff --git a/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptStmt.h b/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptStmt.h index 0b56f39f83..27fbea3ae8 100644 --- a/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptStmt.h +++ b/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptStmt.h @@ -1,953 +1,953 @@ -//===-- ResourceScriptStmt.h ------------------------------------*- C++-*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===---------------------------------------------------------------------===// -// -// This lists all the resource and statement types occurring in RC scripts. -// -//===---------------------------------------------------------------------===// - -#ifndef LLVM_TOOLS_LLVMRC_RESOURCESCRIPTSTMT_H -#define LLVM_TOOLS_LLVMRC_RESOURCESCRIPTSTMT_H - -#include "ResourceScriptToken.h" -#include "ResourceVisitor.h" - -#include "llvm/ADT/StringSet.h" - -namespace llvm { -namespace rc { - -// Integer wrapper that also holds information whether the user declared -// the integer to be long (by appending L to the end of the integer) or not. -// It allows to be implicitly cast from and to uint32_t in order -// to be compatible with the parts of code that don't care about the integers -// being marked long. -class RCInt { - uint32_t Val; - bool Long; - -public: - RCInt(const RCToken &Token) - : Val(Token.intValue()), Long(Token.isLongInt()) {} - RCInt(uint32_t Value) : Val(Value), Long(false) {} - RCInt(uint32_t Value, bool IsLong) : Val(Value), Long(IsLong) {} - operator uint32_t() const { return Val; } - bool isLong() const { return Long; } - - RCInt &operator+=(const RCInt &Rhs) { - std::tie(Val, Long) = std::make_pair(Val + Rhs.Val, Long | Rhs.Long); - return *this; - } - - RCInt &operator-=(const RCInt &Rhs) { - std::tie(Val, Long) = std::make_pair(Val - Rhs.Val, Long | Rhs.Long); - return *this; - } - - RCInt &operator|=(const RCInt &Rhs) { - std::tie(Val, Long) = std::make_pair(Val | Rhs.Val, Long | Rhs.Long); - return *this; - } - - RCInt &operator&=(const RCInt &Rhs) { - std::tie(Val, Long) = std::make_pair(Val & Rhs.Val, Long | Rhs.Long); - return *this; - } - - RCInt operator-() const { return {-Val, Long}; } - RCInt operator~() const { return {~Val, Long}; } - - friend raw_ostream &operator<<(raw_ostream &OS, const RCInt &Int) { - return OS << Int.Val << (Int.Long ? "L" : ""); - } -}; - -class IntWithNotMask { -private: - RCInt Value; - int32_t NotMask; - -public: - IntWithNotMask() : IntWithNotMask(RCInt(0)) {} - IntWithNotMask(RCInt Value, int32_t NotMask = 0) : Value(Value), NotMask(NotMask) {} - - RCInt getValue() const { - return Value; - } - - uint32_t getNotMask() const { - return NotMask; - } - - IntWithNotMask &operator+=(const IntWithNotMask &Rhs) { - Value &= ~Rhs.NotMask; - Value += Rhs.Value; - NotMask |= Rhs.NotMask; - return *this; - } - - IntWithNotMask &operator-=(const IntWithNotMask &Rhs) { - Value &= ~Rhs.NotMask; - Value -= Rhs.Value; - NotMask |= Rhs.NotMask; - return *this; - } - - IntWithNotMask &operator|=(const IntWithNotMask &Rhs) { - Value &= ~Rhs.NotMask; - Value |= Rhs.Value; - NotMask |= Rhs.NotMask; - return *this; - } - - IntWithNotMask &operator&=(const IntWithNotMask &Rhs) { - Value &= ~Rhs.NotMask; - Value &= Rhs.Value; - NotMask |= Rhs.NotMask; - return *this; - } - - IntWithNotMask operator-() const { return {-Value, NotMask}; } - IntWithNotMask operator~() const { return {~Value, 0}; } - - friend raw_ostream &operator<<(raw_ostream &OS, const IntWithNotMask &Int) { - return OS << Int.Value; - } -}; - -// A class holding a name - either an integer or a reference to the string. -class IntOrString { -private: - union Data { - RCInt Int; - StringRef String; - Data(RCInt Value) : Int(Value) {} - Data(const StringRef Value) : String(Value) {} - Data(const RCToken &Token) { - if (Token.kind() == RCToken::Kind::Int) - Int = RCInt(Token); - else - String = Token.value(); - } - } Data; - bool IsInt; - -public: - IntOrString() : IntOrString(RCInt(0)) {} - IntOrString(uint32_t Value) : Data(Value), IsInt(1) {} - IntOrString(RCInt Value) : Data(Value), IsInt(1) {} - IntOrString(StringRef Value) : Data(Value), IsInt(0) {} - IntOrString(const RCToken &Token) - : Data(Token), IsInt(Token.kind() == RCToken::Kind::Int) {} - - bool equalsLower(const char *Str) { - return !IsInt && Data.String.equals_lower(Str); - } - - bool isInt() const { return IsInt; } - - RCInt getInt() const { - assert(IsInt); - return Data.Int; - } - - const StringRef &getString() const { - assert(!IsInt); - return Data.String; - } - - operator Twine() const { - return isInt() ? Twine(getInt()) : Twine(getString()); - } - - friend raw_ostream &operator<<(raw_ostream &, const IntOrString &); -}; - -enum ResourceKind { - // These resource kinds have corresponding .res resource type IDs - // (TYPE in RESOURCEHEADER structure). The numeric value assigned to each - // kind is equal to this type ID. - RkNull = 0, - RkSingleCursor = 1, - RkBitmap = 2, - RkSingleIcon = 3, - RkMenu = 4, - RkDialog = 5, - RkStringTableBundle = 6, - RkAccelerators = 9, - RkRcData = 10, - RkCursorGroup = 12, - RkIconGroup = 14, - RkVersionInfo = 16, - RkHTML = 23, - - // These kinds don't have assigned type IDs (they might be the resources - // of invalid kind, expand to many resource structures in .res files, - // or have variable type ID). In order to avoid ID clashes with IDs above, - // we assign the kinds the values 256 and larger. - RkInvalid = 256, - RkBase, - RkCursor, - RkIcon, - RkStringTable, - RkUser, - RkSingleCursorOrIconRes, - RkCursorOrIconGroupRes, -}; - -// Non-zero memory flags. -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648027(v=vs.85).aspx -enum MemoryFlags { - MfMoveable = 0x10, - MfPure = 0x20, - MfPreload = 0x40, - MfDiscardable = 0x1000 -}; - -// Base resource. All the resources should derive from this base. -class RCResource { -public: - IntOrString ResName; - uint16_t MemoryFlags = getDefaultMemoryFlags(); - void setName(const IntOrString &Name) { ResName = Name; } - virtual raw_ostream &log(raw_ostream &OS) const { - return OS << "Base statement\n"; - }; - RCResource() {} - RCResource(uint16_t Flags) : MemoryFlags(Flags) {} - virtual ~RCResource() {} - - virtual Error visit(Visitor *) const { - llvm_unreachable("This is unable to call methods from Visitor base"); - } - - // Apply the statements attached to this resource. Generic resources - // don't have any. - virtual Error applyStmts(Visitor *) const { return Error::success(); } - - // By default, memory flags are DISCARDABLE | PURE | MOVEABLE. - static uint16_t getDefaultMemoryFlags() { - return MfDiscardable | MfPure | MfMoveable; - } - - virtual ResourceKind getKind() const { return RkBase; } - static bool classof(const RCResource *Res) { return true; } - - virtual IntOrString getResourceType() const { - llvm_unreachable("This cannot be called on objects without types."); - } - virtual Twine getResourceTypeName() const { - llvm_unreachable("This cannot be called on objects without types."); - }; -}; - -// An empty resource. It has no content, type 0, ID 0 and all of its -// characteristics are equal to 0. -class NullResource : public RCResource { -public: - NullResource() : RCResource(0) {} - raw_ostream &log(raw_ostream &OS) const override { - return OS << "Null resource\n"; - } - Error visit(Visitor *V) const override { return V->visitNullResource(this); } - IntOrString getResourceType() const override { return 0; } - Twine getResourceTypeName() const override { return "(NULL)"; } -}; - -// Optional statement base. All such statements should derive from this base. -class OptionalStmt : public RCResource {}; - -class OptionalStmtList : public OptionalStmt { - std::vector<std::unique_ptr<OptionalStmt>> Statements; - -public: - OptionalStmtList() {} - raw_ostream &log(raw_ostream &OS) const override; - - void addStmt(std::unique_ptr<OptionalStmt> Stmt) { - Statements.push_back(std::move(Stmt)); - } - - Error visit(Visitor *V) const override { - for (auto &StmtPtr : Statements) - if (auto Err = StmtPtr->visit(V)) - return Err; - return Error::success(); - } -}; - -class OptStatementsRCResource : public RCResource { -public: - std::unique_ptr<OptionalStmtList> OptStatements; - - OptStatementsRCResource(OptionalStmtList &&Stmts, - uint16_t Flags = RCResource::getDefaultMemoryFlags()) - : RCResource(Flags), - OptStatements(std::make_unique<OptionalStmtList>(std::move(Stmts))) {} - +//===-- ResourceScriptStmt.h ------------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// +// +// This lists all the resource and statement types occurring in RC scripts. +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_TOOLS_LLVMRC_RESOURCESCRIPTSTMT_H +#define LLVM_TOOLS_LLVMRC_RESOURCESCRIPTSTMT_H + +#include "ResourceScriptToken.h" +#include "ResourceVisitor.h" + +#include "llvm/ADT/StringSet.h" + +namespace llvm { +namespace rc { + +// Integer wrapper that also holds information whether the user declared +// the integer to be long (by appending L to the end of the integer) or not. +// It allows to be implicitly cast from and to uint32_t in order +// to be compatible with the parts of code that don't care about the integers +// being marked long. +class RCInt { + uint32_t Val; + bool Long; + +public: + RCInt(const RCToken &Token) + : Val(Token.intValue()), Long(Token.isLongInt()) {} + RCInt(uint32_t Value) : Val(Value), Long(false) {} + RCInt(uint32_t Value, bool IsLong) : Val(Value), Long(IsLong) {} + operator uint32_t() const { return Val; } + bool isLong() const { return Long; } + + RCInt &operator+=(const RCInt &Rhs) { + std::tie(Val, Long) = std::make_pair(Val + Rhs.Val, Long | Rhs.Long); + return *this; + } + + RCInt &operator-=(const RCInt &Rhs) { + std::tie(Val, Long) = std::make_pair(Val - Rhs.Val, Long | Rhs.Long); + return *this; + } + + RCInt &operator|=(const RCInt &Rhs) { + std::tie(Val, Long) = std::make_pair(Val | Rhs.Val, Long | Rhs.Long); + return *this; + } + + RCInt &operator&=(const RCInt &Rhs) { + std::tie(Val, Long) = std::make_pair(Val & Rhs.Val, Long | Rhs.Long); + return *this; + } + + RCInt operator-() const { return {-Val, Long}; } + RCInt operator~() const { return {~Val, Long}; } + + friend raw_ostream &operator<<(raw_ostream &OS, const RCInt &Int) { + return OS << Int.Val << (Int.Long ? "L" : ""); + } +}; + +class IntWithNotMask { +private: + RCInt Value; + int32_t NotMask; + +public: + IntWithNotMask() : IntWithNotMask(RCInt(0)) {} + IntWithNotMask(RCInt Value, int32_t NotMask = 0) : Value(Value), NotMask(NotMask) {} + + RCInt getValue() const { + return Value; + } + + uint32_t getNotMask() const { + return NotMask; + } + + IntWithNotMask &operator+=(const IntWithNotMask &Rhs) { + Value &= ~Rhs.NotMask; + Value += Rhs.Value; + NotMask |= Rhs.NotMask; + return *this; + } + + IntWithNotMask &operator-=(const IntWithNotMask &Rhs) { + Value &= ~Rhs.NotMask; + Value -= Rhs.Value; + NotMask |= Rhs.NotMask; + return *this; + } + + IntWithNotMask &operator|=(const IntWithNotMask &Rhs) { + Value &= ~Rhs.NotMask; + Value |= Rhs.Value; + NotMask |= Rhs.NotMask; + return *this; + } + + IntWithNotMask &operator&=(const IntWithNotMask &Rhs) { + Value &= ~Rhs.NotMask; + Value &= Rhs.Value; + NotMask |= Rhs.NotMask; + return *this; + } + + IntWithNotMask operator-() const { return {-Value, NotMask}; } + IntWithNotMask operator~() const { return {~Value, 0}; } + + friend raw_ostream &operator<<(raw_ostream &OS, const IntWithNotMask &Int) { + return OS << Int.Value; + } +}; + +// A class holding a name - either an integer or a reference to the string. +class IntOrString { +private: + union Data { + RCInt Int; + StringRef String; + Data(RCInt Value) : Int(Value) {} + Data(const StringRef Value) : String(Value) {} + Data(const RCToken &Token) { + if (Token.kind() == RCToken::Kind::Int) + Int = RCInt(Token); + else + String = Token.value(); + } + } Data; + bool IsInt; + +public: + IntOrString() : IntOrString(RCInt(0)) {} + IntOrString(uint32_t Value) : Data(Value), IsInt(1) {} + IntOrString(RCInt Value) : Data(Value), IsInt(1) {} + IntOrString(StringRef Value) : Data(Value), IsInt(0) {} + IntOrString(const RCToken &Token) + : Data(Token), IsInt(Token.kind() == RCToken::Kind::Int) {} + + bool equalsLower(const char *Str) { + return !IsInt && Data.String.equals_lower(Str); + } + + bool isInt() const { return IsInt; } + + RCInt getInt() const { + assert(IsInt); + return Data.Int; + } + + const StringRef &getString() const { + assert(!IsInt); + return Data.String; + } + + operator Twine() const { + return isInt() ? Twine(getInt()) : Twine(getString()); + } + + friend raw_ostream &operator<<(raw_ostream &, const IntOrString &); +}; + +enum ResourceKind { + // These resource kinds have corresponding .res resource type IDs + // (TYPE in RESOURCEHEADER structure). The numeric value assigned to each + // kind is equal to this type ID. + RkNull = 0, + RkSingleCursor = 1, + RkBitmap = 2, + RkSingleIcon = 3, + RkMenu = 4, + RkDialog = 5, + RkStringTableBundle = 6, + RkAccelerators = 9, + RkRcData = 10, + RkCursorGroup = 12, + RkIconGroup = 14, + RkVersionInfo = 16, + RkHTML = 23, + + // These kinds don't have assigned type IDs (they might be the resources + // of invalid kind, expand to many resource structures in .res files, + // or have variable type ID). In order to avoid ID clashes with IDs above, + // we assign the kinds the values 256 and larger. + RkInvalid = 256, + RkBase, + RkCursor, + RkIcon, + RkStringTable, + RkUser, + RkSingleCursorOrIconRes, + RkCursorOrIconGroupRes, +}; + +// Non-zero memory flags. +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648027(v=vs.85).aspx +enum MemoryFlags { + MfMoveable = 0x10, + MfPure = 0x20, + MfPreload = 0x40, + MfDiscardable = 0x1000 +}; + +// Base resource. All the resources should derive from this base. +class RCResource { +public: + IntOrString ResName; + uint16_t MemoryFlags = getDefaultMemoryFlags(); + void setName(const IntOrString &Name) { ResName = Name; } + virtual raw_ostream &log(raw_ostream &OS) const { + return OS << "Base statement\n"; + }; + RCResource() {} + RCResource(uint16_t Flags) : MemoryFlags(Flags) {} + virtual ~RCResource() {} + + virtual Error visit(Visitor *) const { + llvm_unreachable("This is unable to call methods from Visitor base"); + } + + // Apply the statements attached to this resource. Generic resources + // don't have any. + virtual Error applyStmts(Visitor *) const { return Error::success(); } + + // By default, memory flags are DISCARDABLE | PURE | MOVEABLE. + static uint16_t getDefaultMemoryFlags() { + return MfDiscardable | MfPure | MfMoveable; + } + + virtual ResourceKind getKind() const { return RkBase; } + static bool classof(const RCResource *Res) { return true; } + + virtual IntOrString getResourceType() const { + llvm_unreachable("This cannot be called on objects without types."); + } + virtual Twine getResourceTypeName() const { + llvm_unreachable("This cannot be called on objects without types."); + }; +}; + +// An empty resource. It has no content, type 0, ID 0 and all of its +// characteristics are equal to 0. +class NullResource : public RCResource { +public: + NullResource() : RCResource(0) {} + raw_ostream &log(raw_ostream &OS) const override { + return OS << "Null resource\n"; + } + Error visit(Visitor *V) const override { return V->visitNullResource(this); } + IntOrString getResourceType() const override { return 0; } + Twine getResourceTypeName() const override { return "(NULL)"; } +}; + +// Optional statement base. All such statements should derive from this base. +class OptionalStmt : public RCResource {}; + +class OptionalStmtList : public OptionalStmt { + std::vector<std::unique_ptr<OptionalStmt>> Statements; + +public: + OptionalStmtList() {} + raw_ostream &log(raw_ostream &OS) const override; + + void addStmt(std::unique_ptr<OptionalStmt> Stmt) { + Statements.push_back(std::move(Stmt)); + } + + Error visit(Visitor *V) const override { + for (auto &StmtPtr : Statements) + if (auto Err = StmtPtr->visit(V)) + return Err; + return Error::success(); + } +}; + +class OptStatementsRCResource : public RCResource { +public: + std::unique_ptr<OptionalStmtList> OptStatements; + + OptStatementsRCResource(OptionalStmtList &&Stmts, + uint16_t Flags = RCResource::getDefaultMemoryFlags()) + : RCResource(Flags), + OptStatements(std::make_unique<OptionalStmtList>(std::move(Stmts))) {} + Error applyStmts(Visitor *V) const override { return OptStatements->visit(V); } -}; - -// LANGUAGE statement. It can occur both as a top-level statement (in such -// a situation, it changes the default language until the end of the file) -// and as an optional resource statement (then it changes the language -// of a single resource). -// -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381019(v=vs.85).aspx -class LanguageResource : public OptionalStmt { -public: - uint32_t Lang, SubLang; - - LanguageResource(uint32_t LangId, uint32_t SubLangId) - : Lang(LangId), SubLang(SubLangId) {} - raw_ostream &log(raw_ostream &) const override; - - // This is not a regular top-level statement; when it occurs, it just - // modifies the language context. - Error visit(Visitor *V) const override { return V->visitLanguageStmt(this); } - Twine getResourceTypeName() const override { return "LANGUAGE"; } -}; - -// ACCELERATORS resource. Defines a named table of accelerators for the app. -// -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380610(v=vs.85).aspx -class AcceleratorsResource : public OptStatementsRCResource { -public: - class Accelerator { - public: - IntOrString Event; - uint32_t Id; - uint16_t Flags; - - enum Options { - // This is actually 0x0000 (accelerator is assumed to be ASCII if it's - // not VIRTKEY). However, rc.exe behavior is different in situations - // "only ASCII defined" and "neither ASCII nor VIRTKEY defined". - // Therefore, we include ASCII as another flag. This must be zeroed - // when serialized. - ASCII = 0x8000, - VIRTKEY = 0x0001, - NOINVERT = 0x0002, - ALT = 0x0010, - SHIFT = 0x0004, - CONTROL = 0x0008 - }; - - static constexpr size_t NumFlags = 6; - static StringRef OptionsStr[NumFlags]; - static uint32_t OptionsFlags[NumFlags]; - }; - - AcceleratorsResource(OptionalStmtList &&List, uint16_t Flags) - : OptStatementsRCResource(std::move(List), Flags) {} - - std::vector<Accelerator> Accelerators; - - void addAccelerator(IntOrString Event, uint32_t Id, uint16_t Flags) { - Accelerators.push_back(Accelerator{Event, Id, Flags}); - } - raw_ostream &log(raw_ostream &) const override; - - IntOrString getResourceType() const override { return RkAccelerators; } - static uint16_t getDefaultMemoryFlags() { return MfPure | MfMoveable; } - Twine getResourceTypeName() const override { return "ACCELERATORS"; } - - Error visit(Visitor *V) const override { - return V->visitAcceleratorsResource(this); - } - ResourceKind getKind() const override { return RkAccelerators; } - static bool classof(const RCResource *Res) { - return Res->getKind() == RkAccelerators; - } -}; - -// BITMAP resource. Represents a bitmap (".bmp") file. -// -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380680(v=vs.85).aspx -class BitmapResource : public RCResource { -public: - StringRef BitmapLoc; - - BitmapResource(StringRef Location, uint16_t Flags) - : RCResource(Flags), BitmapLoc(Location) {} - raw_ostream &log(raw_ostream &) const override; - - IntOrString getResourceType() const override { return RkBitmap; } - static uint16_t getDefaultMemoryFlags() { return MfPure | MfMoveable; } - - Twine getResourceTypeName() const override { return "BITMAP"; } - Error visit(Visitor *V) const override { - return V->visitBitmapResource(this); - } - ResourceKind getKind() const override { return RkBitmap; } - static bool classof(const RCResource *Res) { - return Res->getKind() == RkBitmap; - } -}; - -// CURSOR resource. Represents a single cursor (".cur") file. -// -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380920(v=vs.85).aspx -class CursorResource : public RCResource { -public: - StringRef CursorLoc; - - CursorResource(StringRef Location, uint16_t Flags) - : RCResource(Flags), CursorLoc(Location) {} - raw_ostream &log(raw_ostream &) const override; - - Twine getResourceTypeName() const override { return "CURSOR"; } - static uint16_t getDefaultMemoryFlags() { return MfDiscardable | MfMoveable; } - Error visit(Visitor *V) const override { - return V->visitCursorResource(this); - } - ResourceKind getKind() const override { return RkCursor; } - static bool classof(const RCResource *Res) { - return Res->getKind() == RkCursor; - } -}; - -// ICON resource. Represents a single ".ico" file containing a group of icons. -// -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381018(v=vs.85).aspx -class IconResource : public RCResource { -public: - StringRef IconLoc; - - IconResource(StringRef Location, uint16_t Flags) - : RCResource(Flags), IconLoc(Location) {} - raw_ostream &log(raw_ostream &) const override; - - Twine getResourceTypeName() const override { return "ICON"; } - static uint16_t getDefaultMemoryFlags() { return MfDiscardable | MfMoveable; } - Error visit(Visitor *V) const override { return V->visitIconResource(this); } - ResourceKind getKind() const override { return RkIcon; } - static bool classof(const RCResource *Res) { - return Res->getKind() == RkIcon; - } -}; - -// HTML resource. Represents a local webpage that is to be embedded into the -// resulting resource file. It embeds a file only - no additional resources -// (images etc.) are included with this resource. -// -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa966018(v=vs.85).aspx -class HTMLResource : public RCResource { -public: - StringRef HTMLLoc; - - HTMLResource(StringRef Location, uint16_t Flags) - : RCResource(Flags), HTMLLoc(Location) {} - raw_ostream &log(raw_ostream &) const override; - - Error visit(Visitor *V) const override { return V->visitHTMLResource(this); } - - // Curiously, file resources don't have DISCARDABLE flag set. - static uint16_t getDefaultMemoryFlags() { return MfPure | MfMoveable; } - IntOrString getResourceType() const override { return RkHTML; } - Twine getResourceTypeName() const override { return "HTML"; } - ResourceKind getKind() const override { return RkHTML; } - static bool classof(const RCResource *Res) { - return Res->getKind() == RkHTML; - } -}; - -// -- MENU resource and its helper classes -- -// This resource describes the contents of an application menu -// (usually located in the upper part of the dialog.) -// -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381025(v=vs.85).aspx - -// Description of a single submenu item. -class MenuDefinition { -public: - enum Options { - CHECKED = 0x0008, - GRAYED = 0x0001, - HELP = 0x4000, - INACTIVE = 0x0002, - MENUBARBREAK = 0x0020, - MENUBREAK = 0x0040 - }; - - enum MenuDefKind { MkBase, MkSeparator, MkMenuItem, MkPopup }; - - static constexpr size_t NumFlags = 6; - static StringRef OptionsStr[NumFlags]; - static uint32_t OptionsFlags[NumFlags]; - static raw_ostream &logFlags(raw_ostream &, uint16_t Flags); - virtual raw_ostream &log(raw_ostream &OS) const { - return OS << "Base menu definition\n"; - } - virtual ~MenuDefinition() {} - - virtual uint16_t getResFlags() const { return 0; } - virtual MenuDefKind getKind() const { return MkBase; } -}; - -// Recursive description of a whole submenu. -class MenuDefinitionList : public MenuDefinition { -public: - std::vector<std::unique_ptr<MenuDefinition>> Definitions; - - void addDefinition(std::unique_ptr<MenuDefinition> Def) { - Definitions.push_back(std::move(Def)); - } - raw_ostream &log(raw_ostream &) const override; -}; - -// Separator in MENU definition (MENUITEM SEPARATOR). -// -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381024(v=vs.85).aspx -class MenuSeparator : public MenuDefinition { -public: - raw_ostream &log(raw_ostream &) const override; - - MenuDefKind getKind() const override { return MkSeparator; } - static bool classof(const MenuDefinition *D) { - return D->getKind() == MkSeparator; - } -}; - -// MENUITEM statement definition. -// -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381024(v=vs.85).aspx -class MenuItem : public MenuDefinition { -public: - StringRef Name; - uint32_t Id; - uint16_t Flags; - - MenuItem(StringRef Caption, uint32_t ItemId, uint16_t ItemFlags) - : Name(Caption), Id(ItemId), Flags(ItemFlags) {} - raw_ostream &log(raw_ostream &) const override; - - uint16_t getResFlags() const override { return Flags; } - MenuDefKind getKind() const override { return MkMenuItem; } - static bool classof(const MenuDefinition *D) { - return D->getKind() == MkMenuItem; - } -}; - -// POPUP statement definition. -// -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381030(v=vs.85).aspx -class PopupItem : public MenuDefinition { -public: - StringRef Name; - uint16_t Flags; - MenuDefinitionList SubItems; - - PopupItem(StringRef Caption, uint16_t ItemFlags, - MenuDefinitionList &&SubItemsList) - : Name(Caption), Flags(ItemFlags), SubItems(std::move(SubItemsList)) {} - raw_ostream &log(raw_ostream &) const override; - - // This has an additional (0x10) flag. It doesn't match with documented - // 0x01 flag, though. - uint16_t getResFlags() const override { return Flags | 0x10; } - MenuDefKind getKind() const override { return MkPopup; } - static bool classof(const MenuDefinition *D) { - return D->getKind() == MkPopup; - } -}; - -// Menu resource definition. -class MenuResource : public OptStatementsRCResource { -public: - MenuDefinitionList Elements; - - MenuResource(OptionalStmtList &&OptStmts, MenuDefinitionList &&Items, - uint16_t Flags) - : OptStatementsRCResource(std::move(OptStmts), Flags), - Elements(std::move(Items)) {} - raw_ostream &log(raw_ostream &) const override; - - IntOrString getResourceType() const override { return RkMenu; } - Twine getResourceTypeName() const override { return "MENU"; } - Error visit(Visitor *V) const override { return V->visitMenuResource(this); } - ResourceKind getKind() const override { return RkMenu; } - static bool classof(const RCResource *Res) { - return Res->getKind() == RkMenu; - } -}; - -// STRINGTABLE resource. Contains a list of strings, each having its unique ID. -// -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381050(v=vs.85).aspx -class StringTableResource : public OptStatementsRCResource { -public: - std::vector<std::pair<uint32_t, std::vector<StringRef>>> Table; - - StringTableResource(OptionalStmtList &&List, uint16_t Flags) - : OptStatementsRCResource(std::move(List), Flags) {} - void addStrings(uint32_t ID, std::vector<StringRef> &&Strings) { - Table.emplace_back(ID, Strings); - } - raw_ostream &log(raw_ostream &) const override; - Twine getResourceTypeName() const override { return "STRINGTABLE"; } - Error visit(Visitor *V) const override { - return V->visitStringTableResource(this); - } -}; - -// -- DIALOG(EX) resource and its helper classes -- -// -// This resource describes dialog boxes and controls residing inside them. -// -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381003(v=vs.85).aspx -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381002(v=vs.85).aspx - -// Single control definition. -class Control { -public: - StringRef Type; - IntOrString Title; - uint32_t ID, X, Y, Width, Height; - Optional<IntWithNotMask> Style; - Optional<uint32_t> ExtStyle, HelpID; - IntOrString Class; - - // Control classes as described in DLGITEMTEMPLATEEX documentation. - // - // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms645389.aspx - enum CtlClasses { - ClsButton = 0x80, - ClsEdit = 0x81, - ClsStatic = 0x82, - ClsListBox = 0x83, - ClsScrollBar = 0x84, - ClsComboBox = 0x85 - }; - - // Simple information about a single control type. - struct CtlInfo { - uint32_t Style; - uint16_t CtlClass; - bool HasTitle; - }; - - Control(StringRef CtlType, IntOrString CtlTitle, uint32_t CtlID, - uint32_t PosX, uint32_t PosY, uint32_t ItemWidth, uint32_t ItemHeight, - Optional<IntWithNotMask> ItemStyle, Optional<uint32_t> ExtItemStyle, - Optional<uint32_t> CtlHelpID, IntOrString CtlClass) - : Type(CtlType), Title(CtlTitle), ID(CtlID), X(PosX), Y(PosY), - Width(ItemWidth), Height(ItemHeight), Style(ItemStyle), - ExtStyle(ExtItemStyle), HelpID(CtlHelpID), Class(CtlClass) {} - - static const StringMap<CtlInfo> SupportedCtls; - - raw_ostream &log(raw_ostream &) const; -}; - -// Single dialog definition. We don't create distinct classes for DIALOG and -// DIALOGEX because of their being too similar to each other. We only have a -// flag determining the type of the dialog box. -class DialogResource : public OptStatementsRCResource { -public: - uint32_t X, Y, Width, Height, HelpID; - std::vector<Control> Controls; - bool IsExtended; - - DialogResource(uint32_t PosX, uint32_t PosY, uint32_t DlgWidth, - uint32_t DlgHeight, uint32_t DlgHelpID, - OptionalStmtList &&OptStmts, bool IsDialogEx, uint16_t Flags) - : OptStatementsRCResource(std::move(OptStmts), Flags), X(PosX), Y(PosY), - Width(DlgWidth), Height(DlgHeight), HelpID(DlgHelpID), - IsExtended(IsDialogEx) {} - - void addControl(Control &&Ctl) { Controls.push_back(std::move(Ctl)); } - - raw_ostream &log(raw_ostream &) const override; - - // It was a weird design decision to assign the same resource type number - // both for DIALOG and DIALOGEX (and the same structure version number). - // It makes it possible for DIALOG to be mistaken for DIALOGEX. - IntOrString getResourceType() const override { return RkDialog; } - Twine getResourceTypeName() const override { - return "DIALOG" + Twine(IsExtended ? "EX" : ""); - } - Error visit(Visitor *V) const override { - return V->visitDialogResource(this); - } - ResourceKind getKind() const override { return RkDialog; } - static bool classof(const RCResource *Res) { - return Res->getKind() == RkDialog; - } -}; - -// User-defined resource. It is either: -// * a link to the file, e.g. NAME TYPE "filename", -// * or contains a list of integers and strings, e.g. NAME TYPE {1, "a", 2}. -class UserDefinedResource : public RCResource { -public: - IntOrString Type; - StringRef FileLoc; - std::vector<IntOrString> Contents; - bool IsFileResource; - - UserDefinedResource(IntOrString ResourceType, StringRef FileLocation, - uint16_t Flags) - : RCResource(Flags), Type(ResourceType), FileLoc(FileLocation), - IsFileResource(true) {} - UserDefinedResource(IntOrString ResourceType, std::vector<IntOrString> &&Data, - uint16_t Flags) - : RCResource(Flags), Type(ResourceType), Contents(std::move(Data)), - IsFileResource(false) {} - - raw_ostream &log(raw_ostream &) const override; - IntOrString getResourceType() const override { return Type; } - Twine getResourceTypeName() const override { return Type; } - static uint16_t getDefaultMemoryFlags() { return MfPure | MfMoveable; } - - Error visit(Visitor *V) const override { - return V->visitUserDefinedResource(this); - } - ResourceKind getKind() const override { return RkUser; } - static bool classof(const RCResource *Res) { - return Res->getKind() == RkUser; - } -}; - -// -- VERSIONINFO resource and its helper classes -- -// -// This resource lists the version information on the executable/library. -// The declaration consists of the following items: -// * A number of fixed optional version statements (e.g. FILEVERSION, FILEOS) -// * BEGIN -// * A number of BLOCK and/or VALUE statements. BLOCK recursively defines -// another block of version information, whereas VALUE defines a -// key -> value correspondence. There might be more than one value -// corresponding to the single key. -// * END -// -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381058(v=vs.85).aspx - -// A single VERSIONINFO statement; -class VersionInfoStmt { -public: - enum StmtKind { StBase = 0, StBlock = 1, StValue = 2 }; - - virtual raw_ostream &log(raw_ostream &OS) const { return OS << "VI stmt\n"; } - virtual ~VersionInfoStmt() {} - - virtual StmtKind getKind() const { return StBase; } - static bool classof(const VersionInfoStmt *S) { - return S->getKind() == StBase; - } -}; - -// BLOCK definition; also the main VERSIONINFO declaration is considered a -// BLOCK, although it has no name. -// The correct top-level blocks are "VarFileInfo" and "StringFileInfo". We don't -// care about them at the parsing phase. -class VersionInfoBlock : public VersionInfoStmt { -public: - std::vector<std::unique_ptr<VersionInfoStmt>> Stmts; - StringRef Name; - - VersionInfoBlock(StringRef BlockName) : Name(BlockName) {} - void addStmt(std::unique_ptr<VersionInfoStmt> Stmt) { - Stmts.push_back(std::move(Stmt)); - } - raw_ostream &log(raw_ostream &) const override; - - StmtKind getKind() const override { return StBlock; } - static bool classof(const VersionInfoStmt *S) { - return S->getKind() == StBlock; - } -}; - -class VersionInfoValue : public VersionInfoStmt { -public: - StringRef Key; - std::vector<IntOrString> Values; - std::vector<bool> HasPrecedingComma; - - VersionInfoValue(StringRef InfoKey, std::vector<IntOrString> &&Vals, - std::vector<bool> &&CommasBeforeVals) - : Key(InfoKey), Values(std::move(Vals)), - HasPrecedingComma(std::move(CommasBeforeVals)) {} - raw_ostream &log(raw_ostream &) const override; - - StmtKind getKind() const override { return StValue; } - static bool classof(const VersionInfoStmt *S) { - return S->getKind() == StValue; - } -}; - -class VersionInfoResource : public RCResource { -public: - // A class listing fixed VERSIONINFO statements (occuring before main BEGIN). - // If any of these is not specified, it is assumed by the original tool to - // be equal to 0. - class VersionInfoFixed { - public: - enum VersionInfoFixedType { - FtUnknown, - FtFileVersion, - FtProductVersion, - FtFileFlagsMask, - FtFileFlags, - FtFileOS, - FtFileType, - FtFileSubtype, - FtNumTypes - }; - - private: - static const StringMap<VersionInfoFixedType> FixedFieldsInfoMap; - static const StringRef FixedFieldsNames[FtNumTypes]; - - public: - SmallVector<uint32_t, 4> FixedInfo[FtNumTypes]; - SmallVector<bool, FtNumTypes> IsTypePresent; - - static VersionInfoFixedType getFixedType(StringRef Type); - static bool isTypeSupported(VersionInfoFixedType Type); - static bool isVersionType(VersionInfoFixedType Type); - - VersionInfoFixed() : IsTypePresent(FtNumTypes, false) {} - - void setValue(VersionInfoFixedType Type, ArrayRef<uint32_t> Value) { - FixedInfo[Type] = SmallVector<uint32_t, 4>(Value.begin(), Value.end()); - IsTypePresent[Type] = true; - } - - raw_ostream &log(raw_ostream &) const; - }; - - VersionInfoBlock MainBlock; - VersionInfoFixed FixedData; - - VersionInfoResource(VersionInfoBlock &&TopLevelBlock, - VersionInfoFixed &&FixedInfo, uint16_t Flags) - : RCResource(Flags), MainBlock(std::move(TopLevelBlock)), - FixedData(std::move(FixedInfo)) {} - - raw_ostream &log(raw_ostream &) const override; - IntOrString getResourceType() const override { return RkVersionInfo; } - static uint16_t getDefaultMemoryFlags() { return MfMoveable | MfPure; } - Twine getResourceTypeName() const override { return "VERSIONINFO"; } - Error visit(Visitor *V) const override { - return V->visitVersionInfoResource(this); - } - ResourceKind getKind() const override { return RkVersionInfo; } - static bool classof(const RCResource *Res) { - return Res->getKind() == RkVersionInfo; - } -}; - -// CHARACTERISTICS optional statement. -// -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380872(v=vs.85).aspx -class CharacteristicsStmt : public OptionalStmt { -public: - uint32_t Value; - - CharacteristicsStmt(uint32_t Characteristic) : Value(Characteristic) {} - raw_ostream &log(raw_ostream &) const override; - - Twine getResourceTypeName() const override { return "CHARACTERISTICS"; } - Error visit(Visitor *V) const override { - return V->visitCharacteristicsStmt(this); - } -}; - -// VERSION optional statement. -// -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381059(v=vs.85).aspx -class VersionStmt : public OptionalStmt { -public: - uint32_t Value; - - VersionStmt(uint32_t Version) : Value(Version) {} - raw_ostream &log(raw_ostream &) const override; - - Twine getResourceTypeName() const override { return "VERSION"; } - Error visit(Visitor *V) const override { return V->visitVersionStmt(this); } -}; - -// CAPTION optional statement. -// -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380778(v=vs.85).aspx -class CaptionStmt : public OptionalStmt { -public: - StringRef Value; - - CaptionStmt(StringRef Caption) : Value(Caption) {} - raw_ostream &log(raw_ostream &) const override; - Twine getResourceTypeName() const override { return "CAPTION"; } - Error visit(Visitor *V) const override { return V->visitCaptionStmt(this); } -}; - -// FONT optional statement. -// Note that the documentation is inaccurate: it expects five arguments to be -// given, however the example provides only two. In fact, the original tool -// expects two arguments - point size and name of the typeface. -// -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381013(v=vs.85).aspx -class FontStmt : public OptionalStmt { -public: - uint32_t Size, Weight, Charset; - StringRef Name; - bool Italic; - - FontStmt(uint32_t FontSize, StringRef FontName, uint32_t FontWeight, - bool FontItalic, uint32_t FontCharset) - : Size(FontSize), Weight(FontWeight), Charset(FontCharset), - Name(FontName), Italic(FontItalic) {} - raw_ostream &log(raw_ostream &) const override; - Twine getResourceTypeName() const override { return "FONT"; } - Error visit(Visitor *V) const override { return V->visitFontStmt(this); } -}; - -// STYLE optional statement. -// -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381051(v=vs.85).aspx -class StyleStmt : public OptionalStmt { -public: - uint32_t Value; - - StyleStmt(uint32_t Style) : Value(Style) {} - raw_ostream &log(raw_ostream &) const override; - Twine getResourceTypeName() const override { return "STYLE"; } - Error visit(Visitor *V) const override { return V->visitStyleStmt(this); } -}; - -// EXSTYLE optional statement. -// -// Ref: docs.microsoft.com/en-us/windows/desktop/menurc/exstyle-statement -class ExStyleStmt : public OptionalStmt { -public: - uint32_t Value; - - ExStyleStmt(uint32_t ExStyle) : Value(ExStyle) {} - raw_ostream &log(raw_ostream &) const override; - Twine getResourceTypeName() const override { return "EXSTYLE"; } - Error visit(Visitor *V) const override { return V->visitExStyleStmt(this); } -}; - -// CLASS optional statement. -// -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380883(v=vs.85).aspx -class ClassStmt : public OptionalStmt { -public: - IntOrString Value; - - ClassStmt(IntOrString Class) : Value(Class) {} - raw_ostream &log(raw_ostream &) const override; - Twine getResourceTypeName() const override { return "CLASS"; } - Error visit(Visitor *V) const override { return V->visitClassStmt(this); } -}; - -} // namespace rc -} // namespace llvm - -#endif +}; + +// LANGUAGE statement. It can occur both as a top-level statement (in such +// a situation, it changes the default language until the end of the file) +// and as an optional resource statement (then it changes the language +// of a single resource). +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381019(v=vs.85).aspx +class LanguageResource : public OptionalStmt { +public: + uint32_t Lang, SubLang; + + LanguageResource(uint32_t LangId, uint32_t SubLangId) + : Lang(LangId), SubLang(SubLangId) {} + raw_ostream &log(raw_ostream &) const override; + + // This is not a regular top-level statement; when it occurs, it just + // modifies the language context. + Error visit(Visitor *V) const override { return V->visitLanguageStmt(this); } + Twine getResourceTypeName() const override { return "LANGUAGE"; } +}; + +// ACCELERATORS resource. Defines a named table of accelerators for the app. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380610(v=vs.85).aspx +class AcceleratorsResource : public OptStatementsRCResource { +public: + class Accelerator { + public: + IntOrString Event; + uint32_t Id; + uint16_t Flags; + + enum Options { + // This is actually 0x0000 (accelerator is assumed to be ASCII if it's + // not VIRTKEY). However, rc.exe behavior is different in situations + // "only ASCII defined" and "neither ASCII nor VIRTKEY defined". + // Therefore, we include ASCII as another flag. This must be zeroed + // when serialized. + ASCII = 0x8000, + VIRTKEY = 0x0001, + NOINVERT = 0x0002, + ALT = 0x0010, + SHIFT = 0x0004, + CONTROL = 0x0008 + }; + + static constexpr size_t NumFlags = 6; + static StringRef OptionsStr[NumFlags]; + static uint32_t OptionsFlags[NumFlags]; + }; + + AcceleratorsResource(OptionalStmtList &&List, uint16_t Flags) + : OptStatementsRCResource(std::move(List), Flags) {} + + std::vector<Accelerator> Accelerators; + + void addAccelerator(IntOrString Event, uint32_t Id, uint16_t Flags) { + Accelerators.push_back(Accelerator{Event, Id, Flags}); + } + raw_ostream &log(raw_ostream &) const override; + + IntOrString getResourceType() const override { return RkAccelerators; } + static uint16_t getDefaultMemoryFlags() { return MfPure | MfMoveable; } + Twine getResourceTypeName() const override { return "ACCELERATORS"; } + + Error visit(Visitor *V) const override { + return V->visitAcceleratorsResource(this); + } + ResourceKind getKind() const override { return RkAccelerators; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkAccelerators; + } +}; + +// BITMAP resource. Represents a bitmap (".bmp") file. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380680(v=vs.85).aspx +class BitmapResource : public RCResource { +public: + StringRef BitmapLoc; + + BitmapResource(StringRef Location, uint16_t Flags) + : RCResource(Flags), BitmapLoc(Location) {} + raw_ostream &log(raw_ostream &) const override; + + IntOrString getResourceType() const override { return RkBitmap; } + static uint16_t getDefaultMemoryFlags() { return MfPure | MfMoveable; } + + Twine getResourceTypeName() const override { return "BITMAP"; } + Error visit(Visitor *V) const override { + return V->visitBitmapResource(this); + } + ResourceKind getKind() const override { return RkBitmap; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkBitmap; + } +}; + +// CURSOR resource. Represents a single cursor (".cur") file. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380920(v=vs.85).aspx +class CursorResource : public RCResource { +public: + StringRef CursorLoc; + + CursorResource(StringRef Location, uint16_t Flags) + : RCResource(Flags), CursorLoc(Location) {} + raw_ostream &log(raw_ostream &) const override; + + Twine getResourceTypeName() const override { return "CURSOR"; } + static uint16_t getDefaultMemoryFlags() { return MfDiscardable | MfMoveable; } + Error visit(Visitor *V) const override { + return V->visitCursorResource(this); + } + ResourceKind getKind() const override { return RkCursor; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkCursor; + } +}; + +// ICON resource. Represents a single ".ico" file containing a group of icons. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381018(v=vs.85).aspx +class IconResource : public RCResource { +public: + StringRef IconLoc; + + IconResource(StringRef Location, uint16_t Flags) + : RCResource(Flags), IconLoc(Location) {} + raw_ostream &log(raw_ostream &) const override; + + Twine getResourceTypeName() const override { return "ICON"; } + static uint16_t getDefaultMemoryFlags() { return MfDiscardable | MfMoveable; } + Error visit(Visitor *V) const override { return V->visitIconResource(this); } + ResourceKind getKind() const override { return RkIcon; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkIcon; + } +}; + +// HTML resource. Represents a local webpage that is to be embedded into the +// resulting resource file. It embeds a file only - no additional resources +// (images etc.) are included with this resource. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa966018(v=vs.85).aspx +class HTMLResource : public RCResource { +public: + StringRef HTMLLoc; + + HTMLResource(StringRef Location, uint16_t Flags) + : RCResource(Flags), HTMLLoc(Location) {} + raw_ostream &log(raw_ostream &) const override; + + Error visit(Visitor *V) const override { return V->visitHTMLResource(this); } + + // Curiously, file resources don't have DISCARDABLE flag set. + static uint16_t getDefaultMemoryFlags() { return MfPure | MfMoveable; } + IntOrString getResourceType() const override { return RkHTML; } + Twine getResourceTypeName() const override { return "HTML"; } + ResourceKind getKind() const override { return RkHTML; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkHTML; + } +}; + +// -- MENU resource and its helper classes -- +// This resource describes the contents of an application menu +// (usually located in the upper part of the dialog.) +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381025(v=vs.85).aspx + +// Description of a single submenu item. +class MenuDefinition { +public: + enum Options { + CHECKED = 0x0008, + GRAYED = 0x0001, + HELP = 0x4000, + INACTIVE = 0x0002, + MENUBARBREAK = 0x0020, + MENUBREAK = 0x0040 + }; + + enum MenuDefKind { MkBase, MkSeparator, MkMenuItem, MkPopup }; + + static constexpr size_t NumFlags = 6; + static StringRef OptionsStr[NumFlags]; + static uint32_t OptionsFlags[NumFlags]; + static raw_ostream &logFlags(raw_ostream &, uint16_t Flags); + virtual raw_ostream &log(raw_ostream &OS) const { + return OS << "Base menu definition\n"; + } + virtual ~MenuDefinition() {} + + virtual uint16_t getResFlags() const { return 0; } + virtual MenuDefKind getKind() const { return MkBase; } +}; + +// Recursive description of a whole submenu. +class MenuDefinitionList : public MenuDefinition { +public: + std::vector<std::unique_ptr<MenuDefinition>> Definitions; + + void addDefinition(std::unique_ptr<MenuDefinition> Def) { + Definitions.push_back(std::move(Def)); + } + raw_ostream &log(raw_ostream &) const override; +}; + +// Separator in MENU definition (MENUITEM SEPARATOR). +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381024(v=vs.85).aspx +class MenuSeparator : public MenuDefinition { +public: + raw_ostream &log(raw_ostream &) const override; + + MenuDefKind getKind() const override { return MkSeparator; } + static bool classof(const MenuDefinition *D) { + return D->getKind() == MkSeparator; + } +}; + +// MENUITEM statement definition. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381024(v=vs.85).aspx +class MenuItem : public MenuDefinition { +public: + StringRef Name; + uint32_t Id; + uint16_t Flags; + + MenuItem(StringRef Caption, uint32_t ItemId, uint16_t ItemFlags) + : Name(Caption), Id(ItemId), Flags(ItemFlags) {} + raw_ostream &log(raw_ostream &) const override; + + uint16_t getResFlags() const override { return Flags; } + MenuDefKind getKind() const override { return MkMenuItem; } + static bool classof(const MenuDefinition *D) { + return D->getKind() == MkMenuItem; + } +}; + +// POPUP statement definition. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381030(v=vs.85).aspx +class PopupItem : public MenuDefinition { +public: + StringRef Name; + uint16_t Flags; + MenuDefinitionList SubItems; + + PopupItem(StringRef Caption, uint16_t ItemFlags, + MenuDefinitionList &&SubItemsList) + : Name(Caption), Flags(ItemFlags), SubItems(std::move(SubItemsList)) {} + raw_ostream &log(raw_ostream &) const override; + + // This has an additional (0x10) flag. It doesn't match with documented + // 0x01 flag, though. + uint16_t getResFlags() const override { return Flags | 0x10; } + MenuDefKind getKind() const override { return MkPopup; } + static bool classof(const MenuDefinition *D) { + return D->getKind() == MkPopup; + } +}; + +// Menu resource definition. +class MenuResource : public OptStatementsRCResource { +public: + MenuDefinitionList Elements; + + MenuResource(OptionalStmtList &&OptStmts, MenuDefinitionList &&Items, + uint16_t Flags) + : OptStatementsRCResource(std::move(OptStmts), Flags), + Elements(std::move(Items)) {} + raw_ostream &log(raw_ostream &) const override; + + IntOrString getResourceType() const override { return RkMenu; } + Twine getResourceTypeName() const override { return "MENU"; } + Error visit(Visitor *V) const override { return V->visitMenuResource(this); } + ResourceKind getKind() const override { return RkMenu; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkMenu; + } +}; + +// STRINGTABLE resource. Contains a list of strings, each having its unique ID. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381050(v=vs.85).aspx +class StringTableResource : public OptStatementsRCResource { +public: + std::vector<std::pair<uint32_t, std::vector<StringRef>>> Table; + + StringTableResource(OptionalStmtList &&List, uint16_t Flags) + : OptStatementsRCResource(std::move(List), Flags) {} + void addStrings(uint32_t ID, std::vector<StringRef> &&Strings) { + Table.emplace_back(ID, Strings); + } + raw_ostream &log(raw_ostream &) const override; + Twine getResourceTypeName() const override { return "STRINGTABLE"; } + Error visit(Visitor *V) const override { + return V->visitStringTableResource(this); + } +}; + +// -- DIALOG(EX) resource and its helper classes -- +// +// This resource describes dialog boxes and controls residing inside them. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381003(v=vs.85).aspx +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381002(v=vs.85).aspx + +// Single control definition. +class Control { +public: + StringRef Type; + IntOrString Title; + uint32_t ID, X, Y, Width, Height; + Optional<IntWithNotMask> Style; + Optional<uint32_t> ExtStyle, HelpID; + IntOrString Class; + + // Control classes as described in DLGITEMTEMPLATEEX documentation. + // + // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms645389.aspx + enum CtlClasses { + ClsButton = 0x80, + ClsEdit = 0x81, + ClsStatic = 0x82, + ClsListBox = 0x83, + ClsScrollBar = 0x84, + ClsComboBox = 0x85 + }; + + // Simple information about a single control type. + struct CtlInfo { + uint32_t Style; + uint16_t CtlClass; + bool HasTitle; + }; + + Control(StringRef CtlType, IntOrString CtlTitle, uint32_t CtlID, + uint32_t PosX, uint32_t PosY, uint32_t ItemWidth, uint32_t ItemHeight, + Optional<IntWithNotMask> ItemStyle, Optional<uint32_t> ExtItemStyle, + Optional<uint32_t> CtlHelpID, IntOrString CtlClass) + : Type(CtlType), Title(CtlTitle), ID(CtlID), X(PosX), Y(PosY), + Width(ItemWidth), Height(ItemHeight), Style(ItemStyle), + ExtStyle(ExtItemStyle), HelpID(CtlHelpID), Class(CtlClass) {} + + static const StringMap<CtlInfo> SupportedCtls; + + raw_ostream &log(raw_ostream &) const; +}; + +// Single dialog definition. We don't create distinct classes for DIALOG and +// DIALOGEX because of their being too similar to each other. We only have a +// flag determining the type of the dialog box. +class DialogResource : public OptStatementsRCResource { +public: + uint32_t X, Y, Width, Height, HelpID; + std::vector<Control> Controls; + bool IsExtended; + + DialogResource(uint32_t PosX, uint32_t PosY, uint32_t DlgWidth, + uint32_t DlgHeight, uint32_t DlgHelpID, + OptionalStmtList &&OptStmts, bool IsDialogEx, uint16_t Flags) + : OptStatementsRCResource(std::move(OptStmts), Flags), X(PosX), Y(PosY), + Width(DlgWidth), Height(DlgHeight), HelpID(DlgHelpID), + IsExtended(IsDialogEx) {} + + void addControl(Control &&Ctl) { Controls.push_back(std::move(Ctl)); } + + raw_ostream &log(raw_ostream &) const override; + + // It was a weird design decision to assign the same resource type number + // both for DIALOG and DIALOGEX (and the same structure version number). + // It makes it possible for DIALOG to be mistaken for DIALOGEX. + IntOrString getResourceType() const override { return RkDialog; } + Twine getResourceTypeName() const override { + return "DIALOG" + Twine(IsExtended ? "EX" : ""); + } + Error visit(Visitor *V) const override { + return V->visitDialogResource(this); + } + ResourceKind getKind() const override { return RkDialog; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkDialog; + } +}; + +// User-defined resource. It is either: +// * a link to the file, e.g. NAME TYPE "filename", +// * or contains a list of integers and strings, e.g. NAME TYPE {1, "a", 2}. +class UserDefinedResource : public RCResource { +public: + IntOrString Type; + StringRef FileLoc; + std::vector<IntOrString> Contents; + bool IsFileResource; + + UserDefinedResource(IntOrString ResourceType, StringRef FileLocation, + uint16_t Flags) + : RCResource(Flags), Type(ResourceType), FileLoc(FileLocation), + IsFileResource(true) {} + UserDefinedResource(IntOrString ResourceType, std::vector<IntOrString> &&Data, + uint16_t Flags) + : RCResource(Flags), Type(ResourceType), Contents(std::move(Data)), + IsFileResource(false) {} + + raw_ostream &log(raw_ostream &) const override; + IntOrString getResourceType() const override { return Type; } + Twine getResourceTypeName() const override { return Type; } + static uint16_t getDefaultMemoryFlags() { return MfPure | MfMoveable; } + + Error visit(Visitor *V) const override { + return V->visitUserDefinedResource(this); + } + ResourceKind getKind() const override { return RkUser; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkUser; + } +}; + +// -- VERSIONINFO resource and its helper classes -- +// +// This resource lists the version information on the executable/library. +// The declaration consists of the following items: +// * A number of fixed optional version statements (e.g. FILEVERSION, FILEOS) +// * BEGIN +// * A number of BLOCK and/or VALUE statements. BLOCK recursively defines +// another block of version information, whereas VALUE defines a +// key -> value correspondence. There might be more than one value +// corresponding to the single key. +// * END +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381058(v=vs.85).aspx + +// A single VERSIONINFO statement; +class VersionInfoStmt { +public: + enum StmtKind { StBase = 0, StBlock = 1, StValue = 2 }; + + virtual raw_ostream &log(raw_ostream &OS) const { return OS << "VI stmt\n"; } + virtual ~VersionInfoStmt() {} + + virtual StmtKind getKind() const { return StBase; } + static bool classof(const VersionInfoStmt *S) { + return S->getKind() == StBase; + } +}; + +// BLOCK definition; also the main VERSIONINFO declaration is considered a +// BLOCK, although it has no name. +// The correct top-level blocks are "VarFileInfo" and "StringFileInfo". We don't +// care about them at the parsing phase. +class VersionInfoBlock : public VersionInfoStmt { +public: + std::vector<std::unique_ptr<VersionInfoStmt>> Stmts; + StringRef Name; + + VersionInfoBlock(StringRef BlockName) : Name(BlockName) {} + void addStmt(std::unique_ptr<VersionInfoStmt> Stmt) { + Stmts.push_back(std::move(Stmt)); + } + raw_ostream &log(raw_ostream &) const override; + + StmtKind getKind() const override { return StBlock; } + static bool classof(const VersionInfoStmt *S) { + return S->getKind() == StBlock; + } +}; + +class VersionInfoValue : public VersionInfoStmt { +public: + StringRef Key; + std::vector<IntOrString> Values; + std::vector<bool> HasPrecedingComma; + + VersionInfoValue(StringRef InfoKey, std::vector<IntOrString> &&Vals, + std::vector<bool> &&CommasBeforeVals) + : Key(InfoKey), Values(std::move(Vals)), + HasPrecedingComma(std::move(CommasBeforeVals)) {} + raw_ostream &log(raw_ostream &) const override; + + StmtKind getKind() const override { return StValue; } + static bool classof(const VersionInfoStmt *S) { + return S->getKind() == StValue; + } +}; + +class VersionInfoResource : public RCResource { +public: + // A class listing fixed VERSIONINFO statements (occuring before main BEGIN). + // If any of these is not specified, it is assumed by the original tool to + // be equal to 0. + class VersionInfoFixed { + public: + enum VersionInfoFixedType { + FtUnknown, + FtFileVersion, + FtProductVersion, + FtFileFlagsMask, + FtFileFlags, + FtFileOS, + FtFileType, + FtFileSubtype, + FtNumTypes + }; + + private: + static const StringMap<VersionInfoFixedType> FixedFieldsInfoMap; + static const StringRef FixedFieldsNames[FtNumTypes]; + + public: + SmallVector<uint32_t, 4> FixedInfo[FtNumTypes]; + SmallVector<bool, FtNumTypes> IsTypePresent; + + static VersionInfoFixedType getFixedType(StringRef Type); + static bool isTypeSupported(VersionInfoFixedType Type); + static bool isVersionType(VersionInfoFixedType Type); + + VersionInfoFixed() : IsTypePresent(FtNumTypes, false) {} + + void setValue(VersionInfoFixedType Type, ArrayRef<uint32_t> Value) { + FixedInfo[Type] = SmallVector<uint32_t, 4>(Value.begin(), Value.end()); + IsTypePresent[Type] = true; + } + + raw_ostream &log(raw_ostream &) const; + }; + + VersionInfoBlock MainBlock; + VersionInfoFixed FixedData; + + VersionInfoResource(VersionInfoBlock &&TopLevelBlock, + VersionInfoFixed &&FixedInfo, uint16_t Flags) + : RCResource(Flags), MainBlock(std::move(TopLevelBlock)), + FixedData(std::move(FixedInfo)) {} + + raw_ostream &log(raw_ostream &) const override; + IntOrString getResourceType() const override { return RkVersionInfo; } + static uint16_t getDefaultMemoryFlags() { return MfMoveable | MfPure; } + Twine getResourceTypeName() const override { return "VERSIONINFO"; } + Error visit(Visitor *V) const override { + return V->visitVersionInfoResource(this); + } + ResourceKind getKind() const override { return RkVersionInfo; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkVersionInfo; + } +}; + +// CHARACTERISTICS optional statement. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380872(v=vs.85).aspx +class CharacteristicsStmt : public OptionalStmt { +public: + uint32_t Value; + + CharacteristicsStmt(uint32_t Characteristic) : Value(Characteristic) {} + raw_ostream &log(raw_ostream &) const override; + + Twine getResourceTypeName() const override { return "CHARACTERISTICS"; } + Error visit(Visitor *V) const override { + return V->visitCharacteristicsStmt(this); + } +}; + +// VERSION optional statement. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381059(v=vs.85).aspx +class VersionStmt : public OptionalStmt { +public: + uint32_t Value; + + VersionStmt(uint32_t Version) : Value(Version) {} + raw_ostream &log(raw_ostream &) const override; + + Twine getResourceTypeName() const override { return "VERSION"; } + Error visit(Visitor *V) const override { return V->visitVersionStmt(this); } +}; + +// CAPTION optional statement. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380778(v=vs.85).aspx +class CaptionStmt : public OptionalStmt { +public: + StringRef Value; + + CaptionStmt(StringRef Caption) : Value(Caption) {} + raw_ostream &log(raw_ostream &) const override; + Twine getResourceTypeName() const override { return "CAPTION"; } + Error visit(Visitor *V) const override { return V->visitCaptionStmt(this); } +}; + +// FONT optional statement. +// Note that the documentation is inaccurate: it expects five arguments to be +// given, however the example provides only two. In fact, the original tool +// expects two arguments - point size and name of the typeface. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381013(v=vs.85).aspx +class FontStmt : public OptionalStmt { +public: + uint32_t Size, Weight, Charset; + StringRef Name; + bool Italic; + + FontStmt(uint32_t FontSize, StringRef FontName, uint32_t FontWeight, + bool FontItalic, uint32_t FontCharset) + : Size(FontSize), Weight(FontWeight), Charset(FontCharset), + Name(FontName), Italic(FontItalic) {} + raw_ostream &log(raw_ostream &) const override; + Twine getResourceTypeName() const override { return "FONT"; } + Error visit(Visitor *V) const override { return V->visitFontStmt(this); } +}; + +// STYLE optional statement. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381051(v=vs.85).aspx +class StyleStmt : public OptionalStmt { +public: + uint32_t Value; + + StyleStmt(uint32_t Style) : Value(Style) {} + raw_ostream &log(raw_ostream &) const override; + Twine getResourceTypeName() const override { return "STYLE"; } + Error visit(Visitor *V) const override { return V->visitStyleStmt(this); } +}; + +// EXSTYLE optional statement. +// +// Ref: docs.microsoft.com/en-us/windows/desktop/menurc/exstyle-statement +class ExStyleStmt : public OptionalStmt { +public: + uint32_t Value; + + ExStyleStmt(uint32_t ExStyle) : Value(ExStyle) {} + raw_ostream &log(raw_ostream &) const override; + Twine getResourceTypeName() const override { return "EXSTYLE"; } + Error visit(Visitor *V) const override { return V->visitExStyleStmt(this); } +}; + +// CLASS optional statement. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380883(v=vs.85).aspx +class ClassStmt : public OptionalStmt { +public: + IntOrString Value; + + ClassStmt(IntOrString Class) : Value(Class) {} + raw_ostream &log(raw_ostream &) const override; + Twine getResourceTypeName() const override { return "CLASS"; } + Error visit(Visitor *V) const override { return V->visitClassStmt(this); } +}; + +} // namespace rc +} // namespace llvm + +#endif diff --git a/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptToken.cpp b/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptToken.cpp index b9699d2b2e..2e21f675b9 100644 --- a/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptToken.cpp +++ b/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptToken.cpp @@ -1,367 +1,367 @@ -//===-- ResourceScriptToken.cpp ---------------------------------*- C++-*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===---------------------------------------------------------------------===// -// -// This file implements an interface defined in ResourceScriptToken.h. -// In particular, it defines an .rc script tokenizer. -// -//===---------------------------------------------------------------------===// - -#include "ResourceScriptToken.h" -#include "llvm/ADT/StringExtras.h" -#include "llvm/Support/raw_ostream.h" - -#include <algorithm> -#include <cassert> -#include <cctype> -#include <cstdlib> -#include <utility> - -using namespace llvm; - -using Kind = RCToken::Kind; - -// Checks if Representation is a correct description of an RC integer. -// It should be a 32-bit unsigned integer, either decimal, octal (0[0-7]+), -// or hexadecimal (0x[0-9a-f]+). It might be followed by a single 'L' -// character (that is the difference between our representation and -// StringRef's one). If Representation is correct, 'true' is returned and -// the return value is put back in Num. -static bool rcGetAsInteger(StringRef Representation, uint32_t &Num) { - size_t Length = Representation.size(); - if (Length == 0) - return false; - // Strip the last 'L' if unnecessary. - if (std::toupper(Representation.back()) == 'L') - Representation = Representation.drop_back(1); - - return !Representation.getAsInteger<uint32_t>(0, Num); -} - -RCToken::RCToken(RCToken::Kind RCTokenKind, StringRef Value) - : TokenKind(RCTokenKind), TokenValue(Value) {} - -uint32_t RCToken::intValue() const { - assert(TokenKind == Kind::Int); - // We assume that the token already is a correct integer (checked by - // rcGetAsInteger). - uint32_t Result; - bool IsSuccess = rcGetAsInteger(TokenValue, Result); - assert(IsSuccess); - (void)IsSuccess; // Silence the compiler warning when -DNDEBUG flag is on. - return Result; -} - -bool RCToken::isLongInt() const { - return TokenKind == Kind::Int && std::toupper(TokenValue.back()) == 'L'; -} - -StringRef RCToken::value() const { return TokenValue; } - -Kind RCToken::kind() const { return TokenKind; } - -bool RCToken::isBinaryOp() const { - switch (TokenKind) { - case Kind::Plus: - case Kind::Minus: - case Kind::Pipe: - case Kind::Amp: - return true; - default: - return false; - } -} - -static Error getStringError(const Twine &message) { - return make_error<StringError>("Error parsing file: " + message, - inconvertibleErrorCode()); -} - -namespace { - -class Tokenizer { -public: - Tokenizer(StringRef Input) : Data(Input), DataLength(Input.size()), Pos(0) {} - - Expected<std::vector<RCToken>> run(); - -private: - // All 'advancing' methods return boolean values; if they're equal to false, - // the stream has ended or failed. - bool advance(size_t Amount = 1); - bool skipWhitespaces(); - - // Consumes a token. If any problem occurred, a non-empty Error is returned. - Error consumeToken(const Kind TokenKind); - - // Check if tokenizer is about to read FollowingChars. - bool willNowRead(StringRef FollowingChars) const; - - // Check if tokenizer can start reading an identifier at current position. - // The original tool did non specify the rules to determine what is a correct - // identifier. We assume they should follow the C convention: - // [a-zA-Z_][a-zA-Z0-9_]*. - bool canStartIdentifier() const; - // Check if tokenizer can continue reading an identifier. - bool canContinueIdentifier() const; - - // Check if tokenizer can start reading an integer. - // A correct integer always starts with a 0-9 digit, - // can contain characters 0-9A-Fa-f (digits), - // Ll (marking the integer is 32-bit), Xx (marking the representation - // is hexadecimal). As some kind of separator should come after the - // integer, we can consume the integer until a non-alphanumeric - // character. - bool canStartInt() const; - bool canContinueInt() const; - - bool canStartString() const; - - // Check if tokenizer can start reading a single line comment (e.g. a comment - // that begins with '//') - bool canStartLineComment() const; - - // Check if tokenizer can start or finish reading a block comment (e.g. a - // comment that begins with '/*' and ends with '*/') - bool canStartBlockComment() const; - - // Throw away all remaining characters on the current line. - void skipCurrentLine(); - - bool streamEof() const; - - // Classify the token that is about to be read from the current position. - Kind classifyCurrentToken() const; - - // Process the Kind::Identifier token - check if it is - // an identifier describing a block start or end. - void processIdentifier(RCToken &token) const; - - StringRef Data; - size_t DataLength, Pos; -}; - -void Tokenizer::skipCurrentLine() { - Pos = Data.find_first_of("\r\n", Pos); - Pos = Data.find_first_not_of("\r\n", Pos); - - if (Pos == StringRef::npos) - Pos = DataLength; -} - -Expected<std::vector<RCToken>> Tokenizer::run() { - Pos = 0; - std::vector<RCToken> Result; - - // Consume an optional UTF-8 Byte Order Mark. - if (willNowRead("\xef\xbb\xbf")) - advance(3); - - while (!streamEof()) { - if (!skipWhitespaces()) - break; - - Kind TokenKind = classifyCurrentToken(); - if (TokenKind == Kind::Invalid) - return getStringError("Invalid token found at position " + Twine(Pos)); - - const size_t TokenStart = Pos; - if (Error TokenError = consumeToken(TokenKind)) - return std::move(TokenError); - - // Comments are just deleted, don't bother saving them. - if (TokenKind == Kind::LineComment || TokenKind == Kind::StartComment) - continue; - - RCToken Token(TokenKind, Data.take_front(Pos).drop_front(TokenStart)); - if (TokenKind == Kind::Identifier) { - processIdentifier(Token); - } else if (TokenKind == Kind::Int) { - uint32_t TokenInt; - if (!rcGetAsInteger(Token.value(), TokenInt)) { - // The integer has incorrect format or cannot be represented in - // a 32-bit integer. - return getStringError("Integer invalid or too large: " + - Token.value().str()); - } - } - - Result.push_back(Token); - } - - return Result; -} - -bool Tokenizer::advance(size_t Amount) { - Pos += Amount; - return !streamEof(); -} - -bool Tokenizer::skipWhitespaces() { - while (!streamEof() && isSpace(Data[Pos])) - advance(); - return !streamEof(); -} - -Error Tokenizer::consumeToken(const Kind TokenKind) { - switch (TokenKind) { - // One-character token consumption. -#define TOKEN(Name) -#define SHORT_TOKEN(Name, Ch) case Kind::Name: -#include "ResourceScriptTokenList.def" - advance(); - return Error::success(); - - case Kind::LineComment: - advance(2); - skipCurrentLine(); - return Error::success(); - - case Kind::StartComment: { - advance(2); - auto EndPos = Data.find("*/", Pos); - if (EndPos == StringRef::npos) - return getStringError( - "Unclosed multi-line comment beginning at position " + Twine(Pos)); - advance(EndPos - Pos); - advance(2); - return Error::success(); - } - case Kind::Identifier: - while (!streamEof() && canContinueIdentifier()) - advance(); - return Error::success(); - - case Kind::Int: - while (!streamEof() && canContinueInt()) - advance(); - return Error::success(); - - case Kind::String: - // Consume the preceding 'L', if there is any. - if (std::toupper(Data[Pos]) == 'L') - advance(); - // Consume the double-quote. - advance(); - - // Consume the characters until the end of the file, line or string. - while (true) { - if (streamEof()) { - return getStringError("Unterminated string literal."); - } else if (Data[Pos] == '"') { - // Consume the ending double-quote. - advance(); - // However, if another '"' follows this double-quote, the string didn't - // end and we just included '"' into the string. - if (!willNowRead("\"")) - return Error::success(); - } else if (Data[Pos] == '\n') { - return getStringError("String literal not terminated in the line."); - } - - advance(); - } - - case Kind::Invalid: - assert(false && "Cannot consume an invalid token."); - } - - llvm_unreachable("Unknown RCToken::Kind"); -} - -bool Tokenizer::willNowRead(StringRef FollowingChars) const { - return Data.drop_front(Pos).startswith(FollowingChars); -} - -bool Tokenizer::canStartIdentifier() const { - assert(!streamEof()); - - const char CurChar = Data[Pos]; - return std::isalpha(CurChar) || CurChar == '_' || CurChar == '.'; -} - -bool Tokenizer::canContinueIdentifier() const { - assert(!streamEof()); - const char CurChar = Data[Pos]; - return std::isalnum(CurChar) || CurChar == '_' || CurChar == '.' || - CurChar == '/' || CurChar == '\\'; -} - -bool Tokenizer::canStartInt() const { - assert(!streamEof()); - return std::isdigit(Data[Pos]); -} - -bool Tokenizer::canStartBlockComment() const { - assert(!streamEof()); - return Data.drop_front(Pos).startswith("/*"); -} - -bool Tokenizer::canStartLineComment() const { - assert(!streamEof()); - return Data.drop_front(Pos).startswith("//"); -} - -bool Tokenizer::canContinueInt() const { - assert(!streamEof()); - return std::isalnum(Data[Pos]); -} - -bool Tokenizer::canStartString() const { - return willNowRead("\"") || willNowRead("L\"") || willNowRead("l\""); -} - -bool Tokenizer::streamEof() const { return Pos == DataLength; } - -Kind Tokenizer::classifyCurrentToken() const { - if (canStartBlockComment()) - return Kind::StartComment; - if (canStartLineComment()) - return Kind::LineComment; - - if (canStartInt()) - return Kind::Int; - if (canStartString()) - return Kind::String; - // BEGIN and END are at this point of lexing recognized as identifiers. - if (canStartIdentifier()) - return Kind::Identifier; - - const char CurChar = Data[Pos]; - - switch (CurChar) { - // One-character token classification. -#define TOKEN(Name) -#define SHORT_TOKEN(Name, Ch) \ - case Ch: \ - return Kind::Name; -#include "ResourceScriptTokenList.def" - - default: - return Kind::Invalid; - } -} - -void Tokenizer::processIdentifier(RCToken &Token) const { - assert(Token.kind() == Kind::Identifier); - StringRef Name = Token.value(); - - if (Name.equals_lower("begin")) - Token = RCToken(Kind::BlockBegin, Name); - else if (Name.equals_lower("end")) - Token = RCToken(Kind::BlockEnd, Name); -} - -} // anonymous namespace - -namespace llvm { - -Expected<std::vector<RCToken>> tokenizeRC(StringRef Input) { - return Tokenizer(Input).run(); -} - -} // namespace llvm +//===-- ResourceScriptToken.cpp ---------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// +// +// This file implements an interface defined in ResourceScriptToken.h. +// In particular, it defines an .rc script tokenizer. +// +//===---------------------------------------------------------------------===// + +#include "ResourceScriptToken.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/raw_ostream.h" + +#include <algorithm> +#include <cassert> +#include <cctype> +#include <cstdlib> +#include <utility> + +using namespace llvm; + +using Kind = RCToken::Kind; + +// Checks if Representation is a correct description of an RC integer. +// It should be a 32-bit unsigned integer, either decimal, octal (0[0-7]+), +// or hexadecimal (0x[0-9a-f]+). It might be followed by a single 'L' +// character (that is the difference between our representation and +// StringRef's one). If Representation is correct, 'true' is returned and +// the return value is put back in Num. +static bool rcGetAsInteger(StringRef Representation, uint32_t &Num) { + size_t Length = Representation.size(); + if (Length == 0) + return false; + // Strip the last 'L' if unnecessary. + if (std::toupper(Representation.back()) == 'L') + Representation = Representation.drop_back(1); + + return !Representation.getAsInteger<uint32_t>(0, Num); +} + +RCToken::RCToken(RCToken::Kind RCTokenKind, StringRef Value) + : TokenKind(RCTokenKind), TokenValue(Value) {} + +uint32_t RCToken::intValue() const { + assert(TokenKind == Kind::Int); + // We assume that the token already is a correct integer (checked by + // rcGetAsInteger). + uint32_t Result; + bool IsSuccess = rcGetAsInteger(TokenValue, Result); + assert(IsSuccess); + (void)IsSuccess; // Silence the compiler warning when -DNDEBUG flag is on. + return Result; +} + +bool RCToken::isLongInt() const { + return TokenKind == Kind::Int && std::toupper(TokenValue.back()) == 'L'; +} + +StringRef RCToken::value() const { return TokenValue; } + +Kind RCToken::kind() const { return TokenKind; } + +bool RCToken::isBinaryOp() const { + switch (TokenKind) { + case Kind::Plus: + case Kind::Minus: + case Kind::Pipe: + case Kind::Amp: + return true; + default: + return false; + } +} + +static Error getStringError(const Twine &message) { + return make_error<StringError>("Error parsing file: " + message, + inconvertibleErrorCode()); +} + +namespace { + +class Tokenizer { +public: + Tokenizer(StringRef Input) : Data(Input), DataLength(Input.size()), Pos(0) {} + + Expected<std::vector<RCToken>> run(); + +private: + // All 'advancing' methods return boolean values; if they're equal to false, + // the stream has ended or failed. + bool advance(size_t Amount = 1); + bool skipWhitespaces(); + + // Consumes a token. If any problem occurred, a non-empty Error is returned. + Error consumeToken(const Kind TokenKind); + + // Check if tokenizer is about to read FollowingChars. + bool willNowRead(StringRef FollowingChars) const; + + // Check if tokenizer can start reading an identifier at current position. + // The original tool did non specify the rules to determine what is a correct + // identifier. We assume they should follow the C convention: + // [a-zA-Z_][a-zA-Z0-9_]*. + bool canStartIdentifier() const; + // Check if tokenizer can continue reading an identifier. + bool canContinueIdentifier() const; + + // Check if tokenizer can start reading an integer. + // A correct integer always starts with a 0-9 digit, + // can contain characters 0-9A-Fa-f (digits), + // Ll (marking the integer is 32-bit), Xx (marking the representation + // is hexadecimal). As some kind of separator should come after the + // integer, we can consume the integer until a non-alphanumeric + // character. + bool canStartInt() const; + bool canContinueInt() const; + + bool canStartString() const; + + // Check if tokenizer can start reading a single line comment (e.g. a comment + // that begins with '//') + bool canStartLineComment() const; + + // Check if tokenizer can start or finish reading a block comment (e.g. a + // comment that begins with '/*' and ends with '*/') + bool canStartBlockComment() const; + + // Throw away all remaining characters on the current line. + void skipCurrentLine(); + + bool streamEof() const; + + // Classify the token that is about to be read from the current position. + Kind classifyCurrentToken() const; + + // Process the Kind::Identifier token - check if it is + // an identifier describing a block start or end. + void processIdentifier(RCToken &token) const; + + StringRef Data; + size_t DataLength, Pos; +}; + +void Tokenizer::skipCurrentLine() { + Pos = Data.find_first_of("\r\n", Pos); + Pos = Data.find_first_not_of("\r\n", Pos); + + if (Pos == StringRef::npos) + Pos = DataLength; +} + +Expected<std::vector<RCToken>> Tokenizer::run() { + Pos = 0; + std::vector<RCToken> Result; + + // Consume an optional UTF-8 Byte Order Mark. + if (willNowRead("\xef\xbb\xbf")) + advance(3); + + while (!streamEof()) { + if (!skipWhitespaces()) + break; + + Kind TokenKind = classifyCurrentToken(); + if (TokenKind == Kind::Invalid) + return getStringError("Invalid token found at position " + Twine(Pos)); + + const size_t TokenStart = Pos; + if (Error TokenError = consumeToken(TokenKind)) + return std::move(TokenError); + + // Comments are just deleted, don't bother saving them. + if (TokenKind == Kind::LineComment || TokenKind == Kind::StartComment) + continue; + + RCToken Token(TokenKind, Data.take_front(Pos).drop_front(TokenStart)); + if (TokenKind == Kind::Identifier) { + processIdentifier(Token); + } else if (TokenKind == Kind::Int) { + uint32_t TokenInt; + if (!rcGetAsInteger(Token.value(), TokenInt)) { + // The integer has incorrect format or cannot be represented in + // a 32-bit integer. + return getStringError("Integer invalid or too large: " + + Token.value().str()); + } + } + + Result.push_back(Token); + } + + return Result; +} + +bool Tokenizer::advance(size_t Amount) { + Pos += Amount; + return !streamEof(); +} + +bool Tokenizer::skipWhitespaces() { + while (!streamEof() && isSpace(Data[Pos])) + advance(); + return !streamEof(); +} + +Error Tokenizer::consumeToken(const Kind TokenKind) { + switch (TokenKind) { + // One-character token consumption. +#define TOKEN(Name) +#define SHORT_TOKEN(Name, Ch) case Kind::Name: +#include "ResourceScriptTokenList.def" + advance(); + return Error::success(); + + case Kind::LineComment: + advance(2); + skipCurrentLine(); + return Error::success(); + + case Kind::StartComment: { + advance(2); + auto EndPos = Data.find("*/", Pos); + if (EndPos == StringRef::npos) + return getStringError( + "Unclosed multi-line comment beginning at position " + Twine(Pos)); + advance(EndPos - Pos); + advance(2); + return Error::success(); + } + case Kind::Identifier: + while (!streamEof() && canContinueIdentifier()) + advance(); + return Error::success(); + + case Kind::Int: + while (!streamEof() && canContinueInt()) + advance(); + return Error::success(); + + case Kind::String: + // Consume the preceding 'L', if there is any. + if (std::toupper(Data[Pos]) == 'L') + advance(); + // Consume the double-quote. + advance(); + + // Consume the characters until the end of the file, line or string. + while (true) { + if (streamEof()) { + return getStringError("Unterminated string literal."); + } else if (Data[Pos] == '"') { + // Consume the ending double-quote. + advance(); + // However, if another '"' follows this double-quote, the string didn't + // end and we just included '"' into the string. + if (!willNowRead("\"")) + return Error::success(); + } else if (Data[Pos] == '\n') { + return getStringError("String literal not terminated in the line."); + } + + advance(); + } + + case Kind::Invalid: + assert(false && "Cannot consume an invalid token."); + } + + llvm_unreachable("Unknown RCToken::Kind"); +} + +bool Tokenizer::willNowRead(StringRef FollowingChars) const { + return Data.drop_front(Pos).startswith(FollowingChars); +} + +bool Tokenizer::canStartIdentifier() const { + assert(!streamEof()); + + const char CurChar = Data[Pos]; + return std::isalpha(CurChar) || CurChar == '_' || CurChar == '.'; +} + +bool Tokenizer::canContinueIdentifier() const { + assert(!streamEof()); + const char CurChar = Data[Pos]; + return std::isalnum(CurChar) || CurChar == '_' || CurChar == '.' || + CurChar == '/' || CurChar == '\\'; +} + +bool Tokenizer::canStartInt() const { + assert(!streamEof()); + return std::isdigit(Data[Pos]); +} + +bool Tokenizer::canStartBlockComment() const { + assert(!streamEof()); + return Data.drop_front(Pos).startswith("/*"); +} + +bool Tokenizer::canStartLineComment() const { + assert(!streamEof()); + return Data.drop_front(Pos).startswith("//"); +} + +bool Tokenizer::canContinueInt() const { + assert(!streamEof()); + return std::isalnum(Data[Pos]); +} + +bool Tokenizer::canStartString() const { + return willNowRead("\"") || willNowRead("L\"") || willNowRead("l\""); +} + +bool Tokenizer::streamEof() const { return Pos == DataLength; } + +Kind Tokenizer::classifyCurrentToken() const { + if (canStartBlockComment()) + return Kind::StartComment; + if (canStartLineComment()) + return Kind::LineComment; + + if (canStartInt()) + return Kind::Int; + if (canStartString()) + return Kind::String; + // BEGIN and END are at this point of lexing recognized as identifiers. + if (canStartIdentifier()) + return Kind::Identifier; + + const char CurChar = Data[Pos]; + + switch (CurChar) { + // One-character token classification. +#define TOKEN(Name) +#define SHORT_TOKEN(Name, Ch) \ + case Ch: \ + return Kind::Name; +#include "ResourceScriptTokenList.def" + + default: + return Kind::Invalid; + } +} + +void Tokenizer::processIdentifier(RCToken &Token) const { + assert(Token.kind() == Kind::Identifier); + StringRef Name = Token.value(); + + if (Name.equals_lower("begin")) + Token = RCToken(Kind::BlockBegin, Name); + else if (Name.equals_lower("end")) + Token = RCToken(Kind::BlockEnd, Name); +} + +} // anonymous namespace + +namespace llvm { + +Expected<std::vector<RCToken>> tokenizeRC(StringRef Input) { + return Tokenizer(Input).run(); +} + +} // namespace llvm diff --git a/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptToken.h b/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptToken.h index d821f10138..cc8ca48b49 100644 --- a/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptToken.h +++ b/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptToken.h @@ -1,81 +1,81 @@ -//===-- ResourceScriptToken.h -----------------------------------*- C++-*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===---------------------------------------------------------------------===// -// -// This declares the .rc script tokens and defines an interface for tokenizing -// the input data. The list of available tokens is located at -// ResourceScriptTokenList.def. -// -// Note that the tokenizer does not support preprocessor directives. The -// preprocessor should do its work on the .rc file before running llvm-rc. -// -// As for now, it is possible to parse ASCII files only (the behavior on -// UTF files might be undefined). However, it already consumes UTF-8 BOM, if -// there is any. Thus, ASCII-compatible UTF-8 files are tokenized correctly. -// -// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380599(v=vs.85).aspx -// -//===---------------------------------------------------------------------===// - -#ifndef LLVM_TOOLS_LLVMRC_RESOURCESCRIPTTOKEN_H -#define LLVM_TOOLS_LLVMRC_RESOURCESCRIPTTOKEN_H - -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/Error.h" - -#include <cstdint> -#include <map> -#include <string> -#include <vector> - -namespace llvm { - -// A definition of a single resource script token. Each token has its kind -// (declared in ResourceScriptTokenList) and holds a value - a reference -// representation of the token. -// RCToken does not claim ownership on its value. A memory buffer containing -// the token value should be stored in a safe place and cannot be freed -// nor reallocated. -class RCToken { -public: - enum class Kind { -#define TOKEN(Name) Name, -#define SHORT_TOKEN(Name, Ch) Name, -#include "ResourceScriptTokenList.def" - }; - - RCToken(RCToken::Kind RCTokenKind, StringRef Value); - - // Get an integer value of the integer token. - uint32_t intValue() const; - bool isLongInt() const; - - StringRef value() const; - Kind kind() const; - - // Check if a token describes a binary operator. - bool isBinaryOp() const; - -private: - Kind TokenKind; - StringRef TokenValue; -}; - -// Tokenize Input. -// In case no error occurred, the return value contains -// tokens in order they were in the input file. -// In case of any error, the return value contains -// a textual representation of error. -// -// Tokens returned by this function hold only references to the parts -// of the Input. Memory buffer containing Input cannot be freed, -// modified or reallocated. -Expected<std::vector<RCToken>> tokenizeRC(StringRef Input); - -} // namespace llvm - -#endif +//===-- ResourceScriptToken.h -----------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// +// +// This declares the .rc script tokens and defines an interface for tokenizing +// the input data. The list of available tokens is located at +// ResourceScriptTokenList.def. +// +// Note that the tokenizer does not support preprocessor directives. The +// preprocessor should do its work on the .rc file before running llvm-rc. +// +// As for now, it is possible to parse ASCII files only (the behavior on +// UTF files might be undefined). However, it already consumes UTF-8 BOM, if +// there is any. Thus, ASCII-compatible UTF-8 files are tokenized correctly. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380599(v=vs.85).aspx +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_TOOLS_LLVMRC_RESOURCESCRIPTTOKEN_H +#define LLVM_TOOLS_LLVMRC_RESOURCESCRIPTTOKEN_H + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" + +#include <cstdint> +#include <map> +#include <string> +#include <vector> + +namespace llvm { + +// A definition of a single resource script token. Each token has its kind +// (declared in ResourceScriptTokenList) and holds a value - a reference +// representation of the token. +// RCToken does not claim ownership on its value. A memory buffer containing +// the token value should be stored in a safe place and cannot be freed +// nor reallocated. +class RCToken { +public: + enum class Kind { +#define TOKEN(Name) Name, +#define SHORT_TOKEN(Name, Ch) Name, +#include "ResourceScriptTokenList.def" + }; + + RCToken(RCToken::Kind RCTokenKind, StringRef Value); + + // Get an integer value of the integer token. + uint32_t intValue() const; + bool isLongInt() const; + + StringRef value() const; + Kind kind() const; + + // Check if a token describes a binary operator. + bool isBinaryOp() const; + +private: + Kind TokenKind; + StringRef TokenValue; +}; + +// Tokenize Input. +// In case no error occurred, the return value contains +// tokens in order they were in the input file. +// In case of any error, the return value contains +// a textual representation of error. +// +// Tokens returned by this function hold only references to the parts +// of the Input. Memory buffer containing Input cannot be freed, +// modified or reallocated. +Expected<std::vector<RCToken>> tokenizeRC(StringRef Input); + +} // namespace llvm + +#endif diff --git a/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptTokenList.def b/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptTokenList.def index e0ea32f940..a61a96461f 100644 --- a/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptTokenList.def +++ b/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptTokenList.def @@ -1,39 +1,39 @@ -//===-- ResourceScriptTokenList.h -------------------------------*- C++-*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===---------------------------------------------------------------------===// -// -// This is a part of llvm-rc tokenizer. It lists all the possible tokens -// that might occur in a correct .rc script. -// -//===---------------------------------------------------------------------===// - - -// Long tokens. They might consist of more than one character. -TOKEN(Invalid) // Invalid token. Should not occur in a valid script. -TOKEN(Int) // Integer (decimal, octal or hexadecimal). -TOKEN(String) // String value. -TOKEN(Identifier) // Script identifier (resource name or type). -TOKEN(LineComment) // Beginning of single-line comment. -TOKEN(StartComment) // Beginning of multi-line comment. - -// Short tokens. They usually consist of exactly one character. -// The definitions are of the form SHORT_TOKEN(TokenName, TokenChar). -// TokenChar is the one-character token representation occuring in the correct -// .rc scripts. -SHORT_TOKEN(BlockBegin, '{') // Start of the script block; can also be BEGIN. -SHORT_TOKEN(BlockEnd, '}') // End of the block; can also be END. -SHORT_TOKEN(Comma, ',') // Comma - resource arguments separator. -SHORT_TOKEN(Plus, '+') // Addition operator. -SHORT_TOKEN(Minus, '-') // Subtraction operator. -SHORT_TOKEN(Pipe, '|') // Bitwise-OR operator. -SHORT_TOKEN(Amp, '&') // Bitwise-AND operator. -SHORT_TOKEN(Tilde, '~') // Bitwise-NOT operator. -SHORT_TOKEN(LeftParen, '(') // Left parenthesis in the script expressions. -SHORT_TOKEN(RightParen, ')') // Right parenthesis. - -#undef TOKEN -#undef SHORT_TOKEN +//===-- ResourceScriptTokenList.h -------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// +// +// This is a part of llvm-rc tokenizer. It lists all the possible tokens +// that might occur in a correct .rc script. +// +//===---------------------------------------------------------------------===// + + +// Long tokens. They might consist of more than one character. +TOKEN(Invalid) // Invalid token. Should not occur in a valid script. +TOKEN(Int) // Integer (decimal, octal or hexadecimal). +TOKEN(String) // String value. +TOKEN(Identifier) // Script identifier (resource name or type). +TOKEN(LineComment) // Beginning of single-line comment. +TOKEN(StartComment) // Beginning of multi-line comment. + +// Short tokens. They usually consist of exactly one character. +// The definitions are of the form SHORT_TOKEN(TokenName, TokenChar). +// TokenChar is the one-character token representation occuring in the correct +// .rc scripts. +SHORT_TOKEN(BlockBegin, '{') // Start of the script block; can also be BEGIN. +SHORT_TOKEN(BlockEnd, '}') // End of the block; can also be END. +SHORT_TOKEN(Comma, ',') // Comma - resource arguments separator. +SHORT_TOKEN(Plus, '+') // Addition operator. +SHORT_TOKEN(Minus, '-') // Subtraction operator. +SHORT_TOKEN(Pipe, '|') // Bitwise-OR operator. +SHORT_TOKEN(Amp, '&') // Bitwise-AND operator. +SHORT_TOKEN(Tilde, '~') // Bitwise-NOT operator. +SHORT_TOKEN(LeftParen, '(') // Left parenthesis in the script expressions. +SHORT_TOKEN(RightParen, ')') // Right parenthesis. + +#undef TOKEN +#undef SHORT_TOKEN diff --git a/contrib/libs/llvm12/tools/llvm-rc/ResourceVisitor.h b/contrib/libs/llvm12/tools/llvm-rc/ResourceVisitor.h index f45154ddd4..843c8d898a 100644 --- a/contrib/libs/llvm12/tools/llvm-rc/ResourceVisitor.h +++ b/contrib/libs/llvm12/tools/llvm-rc/ResourceVisitor.h @@ -1,61 +1,61 @@ -//===-- ResourceVisitor.h ---------------------------------------*- C++-*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===---------------------------------------------------------------------===// -// -// This defines a base class visiting resource script resources. -// -//===---------------------------------------------------------------------===// - -#ifndef LLVM_TOOLS_LLVMRC_RESOURCEVISITOR_H -#define LLVM_TOOLS_LLVMRC_RESOURCEVISITOR_H - -#include "llvm/Support/Error.h" - -namespace llvm { -namespace rc { - -class RCResource; - -class CaptionStmt; -class ClassStmt; -class CharacteristicsStmt; -class ExStyleStmt; -class FontStmt; -class LanguageResource; -class StyleStmt; -class VersionStmt; - -class Visitor { -public: - virtual Error visitNullResource(const RCResource *) = 0; - virtual Error visitAcceleratorsResource(const RCResource *) = 0; - virtual Error visitBitmapResource(const RCResource *) = 0; - virtual Error visitCursorResource(const RCResource *) = 0; - virtual Error visitDialogResource(const RCResource *) = 0; - virtual Error visitHTMLResource(const RCResource *) = 0; - virtual Error visitIconResource(const RCResource *) = 0; - virtual Error visitMenuResource(const RCResource *) = 0; - virtual Error visitStringTableResource(const RCResource *) = 0; - virtual Error visitUserDefinedResource(const RCResource *) = 0; - virtual Error visitVersionInfoResource(const RCResource *) = 0; - - virtual Error visitCaptionStmt(const CaptionStmt *) = 0; - virtual Error visitClassStmt(const ClassStmt *) = 0; - virtual Error visitCharacteristicsStmt(const CharacteristicsStmt *) = 0; - virtual Error visitExStyleStmt(const ExStyleStmt *) = 0; - virtual Error visitFontStmt(const FontStmt *) = 0; - virtual Error visitLanguageStmt(const LanguageResource *) = 0; - virtual Error visitStyleStmt(const StyleStmt *) = 0; - virtual Error visitVersionStmt(const VersionStmt *) = 0; - - virtual ~Visitor() {} -}; - -} // namespace rc -} // namespace llvm - -#endif +//===-- ResourceVisitor.h ---------------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// +// +// This defines a base class visiting resource script resources. +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_TOOLS_LLVMRC_RESOURCEVISITOR_H +#define LLVM_TOOLS_LLVMRC_RESOURCEVISITOR_H + +#include "llvm/Support/Error.h" + +namespace llvm { +namespace rc { + +class RCResource; + +class CaptionStmt; +class ClassStmt; +class CharacteristicsStmt; +class ExStyleStmt; +class FontStmt; +class LanguageResource; +class StyleStmt; +class VersionStmt; + +class Visitor { +public: + virtual Error visitNullResource(const RCResource *) = 0; + virtual Error visitAcceleratorsResource(const RCResource *) = 0; + virtual Error visitBitmapResource(const RCResource *) = 0; + virtual Error visitCursorResource(const RCResource *) = 0; + virtual Error visitDialogResource(const RCResource *) = 0; + virtual Error visitHTMLResource(const RCResource *) = 0; + virtual Error visitIconResource(const RCResource *) = 0; + virtual Error visitMenuResource(const RCResource *) = 0; + virtual Error visitStringTableResource(const RCResource *) = 0; + virtual Error visitUserDefinedResource(const RCResource *) = 0; + virtual Error visitVersionInfoResource(const RCResource *) = 0; + + virtual Error visitCaptionStmt(const CaptionStmt *) = 0; + virtual Error visitClassStmt(const ClassStmt *) = 0; + virtual Error visitCharacteristicsStmt(const CharacteristicsStmt *) = 0; + virtual Error visitExStyleStmt(const ExStyleStmt *) = 0; + virtual Error visitFontStmt(const FontStmt *) = 0; + virtual Error visitLanguageStmt(const LanguageResource *) = 0; + virtual Error visitStyleStmt(const StyleStmt *) = 0; + virtual Error visitVersionStmt(const VersionStmt *) = 0; + + virtual ~Visitor() {} +}; + +} // namespace rc +} // namespace llvm + +#endif diff --git a/contrib/libs/llvm12/tools/llvm-rc/llvm-rc.cpp b/contrib/libs/llvm12/tools/llvm-rc/llvm-rc.cpp index ced68a3727..e9027a21d4 100644 --- a/contrib/libs/llvm12/tools/llvm-rc/llvm-rc.cpp +++ b/contrib/libs/llvm12/tools/llvm-rc/llvm-rc.cpp @@ -1,216 +1,216 @@ -//===-- llvm-rc.cpp - Compile .rc scripts into .res -------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// Compile .rc scripts into .res files. This is intended to be a -// platform-independent port of Microsoft's rc.exe tool. -// -//===----------------------------------------------------------------------===// - -#include "ResourceFileWriter.h" -#include "ResourceScriptCppFilter.h" -#include "ResourceScriptParser.h" -#include "ResourceScriptStmt.h" -#include "ResourceScriptToken.h" - -#include "llvm/Option/Arg.h" -#include "llvm/Option/ArgList.h" -#include "llvm/Support/Error.h" -#include "llvm/Support/FileSystem.h" -#include "llvm/Support/InitLLVM.h" -#include "llvm/Support/ManagedStatic.h" -#include "llvm/Support/MemoryBuffer.h" -#include "llvm/Support/Path.h" -#include "llvm/Support/PrettyStackTrace.h" -#include "llvm/Support/Process.h" -#include "llvm/Support/Signals.h" -#include "llvm/Support/raw_ostream.h" - -#include <algorithm> -#include <system_error> - -using namespace llvm; -using namespace llvm::rc; - -namespace { - -// Input options tables. - -enum ID { - OPT_INVALID = 0, // This is not a correct option ID. -#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ - HELPTEXT, METAVAR, VALUES) \ - OPT_##ID, -#include "Opts.inc" -#undef OPTION -}; - -#define PREFIX(NAME, VALUE) const char *const NAME[] = VALUE; -#include "Opts.inc" -#undef PREFIX - -static const opt::OptTable::Info InfoTable[] = { -#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ - HELPTEXT, METAVAR, VALUES) \ - { \ - PREFIX, NAME, HELPTEXT, \ - METAVAR, OPT_##ID, opt::Option::KIND##Class, \ - PARAM, FLAGS, OPT_##GROUP, \ - OPT_##ALIAS, ALIASARGS, VALUES}, -#include "Opts.inc" -#undef OPTION -}; - -class RcOptTable : public opt::OptTable { -public: - RcOptTable() : OptTable(InfoTable, /* IgnoreCase = */ true) {} -}; - -static ExitOnError ExitOnErr; - -LLVM_ATTRIBUTE_NORETURN static void fatalError(const Twine &Message) { - errs() << Message << "\n"; - exit(1); -} - -} // anonymous namespace - -int main(int Argc, const char **Argv) { - InitLLVM X(Argc, Argv); - ExitOnErr.setBanner("llvm-rc: "); - - RcOptTable T; - unsigned MAI, MAC; - const char **DashDash = std::find_if( - Argv + 1, Argv + Argc, [](StringRef Str) { return Str == "--"; }); - ArrayRef<const char *> ArgsArr = makeArrayRef(Argv + 1, DashDash); - - opt::InputArgList InputArgs = T.ParseArgs(ArgsArr, MAI, MAC); - - // The tool prints nothing when invoked with no command-line arguments. +//===-- llvm-rc.cpp - Compile .rc scripts into .res -------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Compile .rc scripts into .res files. This is intended to be a +// platform-independent port of Microsoft's rc.exe tool. +// +//===----------------------------------------------------------------------===// + +#include "ResourceFileWriter.h" +#include "ResourceScriptCppFilter.h" +#include "ResourceScriptParser.h" +#include "ResourceScriptStmt.h" +#include "ResourceScriptToken.h" + +#include "llvm/Option/Arg.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/InitLLVM.h" +#include "llvm/Support/ManagedStatic.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/PrettyStackTrace.h" +#include "llvm/Support/Process.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/raw_ostream.h" + +#include <algorithm> +#include <system_error> + +using namespace llvm; +using namespace llvm::rc; + +namespace { + +// Input options tables. + +enum ID { + OPT_INVALID = 0, // This is not a correct option ID. +#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ + HELPTEXT, METAVAR, VALUES) \ + OPT_##ID, +#include "Opts.inc" +#undef OPTION +}; + +#define PREFIX(NAME, VALUE) const char *const NAME[] = VALUE; +#include "Opts.inc" +#undef PREFIX + +static const opt::OptTable::Info InfoTable[] = { +#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ + HELPTEXT, METAVAR, VALUES) \ + { \ + PREFIX, NAME, HELPTEXT, \ + METAVAR, OPT_##ID, opt::Option::KIND##Class, \ + PARAM, FLAGS, OPT_##GROUP, \ + OPT_##ALIAS, ALIASARGS, VALUES}, +#include "Opts.inc" +#undef OPTION +}; + +class RcOptTable : public opt::OptTable { +public: + RcOptTable() : OptTable(InfoTable, /* IgnoreCase = */ true) {} +}; + +static ExitOnError ExitOnErr; + +LLVM_ATTRIBUTE_NORETURN static void fatalError(const Twine &Message) { + errs() << Message << "\n"; + exit(1); +} + +} // anonymous namespace + +int main(int Argc, const char **Argv) { + InitLLVM X(Argc, Argv); + ExitOnErr.setBanner("llvm-rc: "); + + RcOptTable T; + unsigned MAI, MAC; + const char **DashDash = std::find_if( + Argv + 1, Argv + Argc, [](StringRef Str) { return Str == "--"; }); + ArrayRef<const char *> ArgsArr = makeArrayRef(Argv + 1, DashDash); + + opt::InputArgList InputArgs = T.ParseArgs(ArgsArr, MAI, MAC); + + // The tool prints nothing when invoked with no command-line arguments. if (InputArgs.hasArg(OPT_help)) { - T.PrintHelp(outs(), "rc [options] file...", "Resource Converter", false); - return 0; - } - + T.PrintHelp(outs(), "rc [options] file...", "Resource Converter", false); + return 0; + } + const bool BeVerbose = InputArgs.hasArg(OPT_verbose); - - std::vector<std::string> InArgsInfo = InputArgs.getAllArgValues(OPT_INPUT); - if (DashDash != Argv + Argc) - InArgsInfo.insert(InArgsInfo.end(), DashDash + 1, Argv + Argc); - if (InArgsInfo.size() != 1) { - fatalError("Exactly one input file should be provided."); - } - - // Read and tokenize the input file. - ErrorOr<std::unique_ptr<MemoryBuffer>> File = - MemoryBuffer::getFile(InArgsInfo[0]); - if (!File) { - fatalError("Error opening file '" + Twine(InArgsInfo[0]) + - "': " + File.getError().message()); - } - - std::unique_ptr<MemoryBuffer> FileContents = std::move(*File); - StringRef Contents = FileContents->getBuffer(); - - std::string FilteredContents = filterCppOutput(Contents); - std::vector<RCToken> Tokens = ExitOnErr(tokenizeRC(FilteredContents)); - - if (BeVerbose) { - const Twine TokenNames[] = { -#define TOKEN(Name) #Name, -#define SHORT_TOKEN(Name, Ch) #Name, -#include "ResourceScriptTokenList.def" - }; - - for (const RCToken &Token : Tokens) { - outs() << TokenNames[static_cast<int>(Token.kind())] << ": " - << Token.value(); - if (Token.kind() == RCToken::Kind::Int) - outs() << "; int value = " << Token.intValue(); - - outs() << "\n"; - } - } - - WriterParams Params; - SmallString<128> InputFile(InArgsInfo[0]); - llvm::sys::fs::make_absolute(InputFile); - Params.InputFilePath = InputFile; + + std::vector<std::string> InArgsInfo = InputArgs.getAllArgValues(OPT_INPUT); + if (DashDash != Argv + Argc) + InArgsInfo.insert(InArgsInfo.end(), DashDash + 1, Argv + Argc); + if (InArgsInfo.size() != 1) { + fatalError("Exactly one input file should be provided."); + } + + // Read and tokenize the input file. + ErrorOr<std::unique_ptr<MemoryBuffer>> File = + MemoryBuffer::getFile(InArgsInfo[0]); + if (!File) { + fatalError("Error opening file '" + Twine(InArgsInfo[0]) + + "': " + File.getError().message()); + } + + std::unique_ptr<MemoryBuffer> FileContents = std::move(*File); + StringRef Contents = FileContents->getBuffer(); + + std::string FilteredContents = filterCppOutput(Contents); + std::vector<RCToken> Tokens = ExitOnErr(tokenizeRC(FilteredContents)); + + if (BeVerbose) { + const Twine TokenNames[] = { +#define TOKEN(Name) #Name, +#define SHORT_TOKEN(Name, Ch) #Name, +#include "ResourceScriptTokenList.def" + }; + + for (const RCToken &Token : Tokens) { + outs() << TokenNames[static_cast<int>(Token.kind())] << ": " + << Token.value(); + if (Token.kind() == RCToken::Kind::Int) + outs() << "; int value = " << Token.intValue(); + + outs() << "\n"; + } + } + + WriterParams Params; + SmallString<128> InputFile(InArgsInfo[0]); + llvm::sys::fs::make_absolute(InputFile); + Params.InputFilePath = InputFile; Params.Include = InputArgs.getAllArgValues(OPT_includepath); Params.NoInclude = InputArgs.getAllArgValues(OPT_noinclude); - + if (InputArgs.hasArg(OPT_codepage)) { if (InputArgs.getLastArgValue(OPT_codepage) - .getAsInteger(10, Params.CodePage)) - fatalError("Invalid code page: " + + .getAsInteger(10, Params.CodePage)) + fatalError("Invalid code page: " + InputArgs.getLastArgValue(OPT_codepage)); - switch (Params.CodePage) { - case CpAcp: - case CpWin1252: - case CpUtf8: - break; - default: - fatalError( - "Unsupported code page, only 0, 1252 and 65001 are supported!"); - } - } - - std::unique_ptr<ResourceFileWriter> Visitor; + switch (Params.CodePage) { + case CpAcp: + case CpWin1252: + case CpUtf8: + break; + default: + fatalError( + "Unsupported code page, only 0, 1252 and 65001 are supported!"); + } + } + + std::unique_ptr<ResourceFileWriter> Visitor; bool IsDryRun = InputArgs.hasArg(OPT_dry_run); - - if (!IsDryRun) { + + if (!IsDryRun) { auto OutArgsInfo = InputArgs.getAllArgValues(OPT_fileout); - if (OutArgsInfo.empty()) { - SmallString<128> OutputFile = InputFile; - llvm::sys::path::replace_extension(OutputFile, "res"); - OutArgsInfo.push_back(std::string(OutputFile.str())); - } - - if (OutArgsInfo.size() != 1) - fatalError( - "No more than one output file should be provided (using /FO flag)."); - - std::error_code EC; - auto FOut = std::make_unique<raw_fd_ostream>( - OutArgsInfo[0], EC, sys::fs::FA_Read | sys::fs::FA_Write); - if (EC) - fatalError("Error opening output file '" + OutArgsInfo[0] + - "': " + EC.message()); - Visitor = std::make_unique<ResourceFileWriter>(Params, std::move(FOut)); + if (OutArgsInfo.empty()) { + SmallString<128> OutputFile = InputFile; + llvm::sys::path::replace_extension(OutputFile, "res"); + OutArgsInfo.push_back(std::string(OutputFile.str())); + } + + if (OutArgsInfo.size() != 1) + fatalError( + "No more than one output file should be provided (using /FO flag)."); + + std::error_code EC; + auto FOut = std::make_unique<raw_fd_ostream>( + OutArgsInfo[0], EC, sys::fs::FA_Read | sys::fs::FA_Write); + if (EC) + fatalError("Error opening output file '" + OutArgsInfo[0] + + "': " + EC.message()); + Visitor = std::make_unique<ResourceFileWriter>(Params, std::move(FOut)); Visitor->AppendNull = InputArgs.hasArg(OPT_add_null); - - ExitOnErr(NullResource().visit(Visitor.get())); - - // Set the default language; choose en-US arbitrarily. - unsigned PrimaryLangId = 0x09, SubLangId = 0x01; + + ExitOnErr(NullResource().visit(Visitor.get())); + + // Set the default language; choose en-US arbitrarily. + unsigned PrimaryLangId = 0x09, SubLangId = 0x01; if (InputArgs.hasArg(OPT_lang_id)) { - unsigned LangId; + unsigned LangId; if (InputArgs.getLastArgValue(OPT_lang_id).getAsInteger(16, LangId)) - fatalError("Invalid language id: " + + fatalError("Invalid language id: " + InputArgs.getLastArgValue(OPT_lang_id)); - PrimaryLangId = LangId & 0x3ff; - SubLangId = LangId >> 10; - } - ExitOnErr(LanguageResource(PrimaryLangId, SubLangId).visit(Visitor.get())); - } - - rc::RCParser Parser{std::move(Tokens)}; - while (!Parser.isEof()) { - auto Resource = ExitOnErr(Parser.parseSingleResource()); - if (BeVerbose) - Resource->log(outs()); - if (!IsDryRun) - ExitOnErr(Resource->visit(Visitor.get())); - } - - // STRINGTABLE resources come at the very end. - if (!IsDryRun) - ExitOnErr(Visitor->dumpAllStringTables()); - - return 0; -} + PrimaryLangId = LangId & 0x3ff; + SubLangId = LangId >> 10; + } + ExitOnErr(LanguageResource(PrimaryLangId, SubLangId).visit(Visitor.get())); + } + + rc::RCParser Parser{std::move(Tokens)}; + while (!Parser.isEof()) { + auto Resource = ExitOnErr(Parser.parseSingleResource()); + if (BeVerbose) + Resource->log(outs()); + if (!IsDryRun) + ExitOnErr(Resource->visit(Visitor.get())); + } + + // STRINGTABLE resources come at the very end. + if (!IsDryRun) + ExitOnErr(Visitor->dumpAllStringTables()); + + return 0; +} diff --git a/contrib/libs/llvm12/tools/llvm-rc/ya.make b/contrib/libs/llvm12/tools/llvm-rc/ya.make index e1f1b87aec..e69f41f542 100644 --- a/contrib/libs/llvm12/tools/llvm-rc/ya.make +++ b/contrib/libs/llvm12/tools/llvm-rc/ya.make @@ -1,40 +1,40 @@ -# Generated by devtools/yamaker. - -PROGRAM() - +# Generated by devtools/yamaker. + +PROGRAM() + OWNER( orivej g:cpp-contrib ) - + LICENSE(Apache-2.0 WITH LLVM-exception) - + LICENSE_TEXTS(.yandex_meta/licenses.list.txt) -PEERDIR( +PEERDIR( contrib/libs/llvm12 contrib/libs/llvm12/include contrib/libs/llvm12/lib/Demangle contrib/libs/llvm12/lib/Option contrib/libs/llvm12/lib/Support -) - -ADDINCL( +) + +ADDINCL( ${ARCADIA_BUILD_ROOT}/contrib/libs/llvm12/tools/llvm-rc contrib/libs/llvm12/tools/llvm-rc -) - -NO_COMPILER_WARNINGS() - -NO_UTIL() - -SRCS( - ResourceFileWriter.cpp - ResourceScriptCppFilter.cpp - ResourceScriptParser.cpp - ResourceScriptStmt.cpp - ResourceScriptToken.cpp - llvm-rc.cpp -) - -END() +) + +NO_COMPILER_WARNINGS() + +NO_UTIL() + +SRCS( + ResourceFileWriter.cpp + ResourceScriptCppFilter.cpp + ResourceScriptParser.cpp + ResourceScriptStmt.cpp + ResourceScriptToken.cpp + llvm-rc.cpp +) + +END() |