Index: llvm/test/tools/llvm-rc/Inputs/output-boundary-values-error-1.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/output-boundary-values-error-1.rc @@ -0,0 +1 @@ +LANGUAGE 1024, 10 Index: llvm/test/tools/llvm-rc/Inputs/output-boundary-values-error-2.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/output-boundary-values-error-2.rc @@ -0,0 +1 @@ +LANGUAGE 500, 64 Index: llvm/test/tools/llvm-rc/Inputs/output-boundary-values.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/output-boundary-values.rc @@ -0,0 +1 @@ +LANGUAGE 1023, 63 Index: llvm/test/tools/llvm-rc/Inputs/tag-html.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/tag-html.rc @@ -0,0 +1,2 @@ +100 HTML "webpage1.html" +Kitten HTML "webpage2.html" Index: llvm/test/tools/llvm-rc/Inputs/webpage1.html =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/webpage1.html @@ -0,0 +1,5 @@ + + + Hello! + + Index: llvm/test/tools/llvm-rc/Inputs/webpage2.html =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/webpage2.html @@ -0,0 +1,2 @@ + + Index: llvm/test/tools/llvm-rc/helpmsg.test =================================================================== --- llvm/test/tools/llvm-rc/helpmsg.test +++ llvm/test/tools/llvm-rc/helpmsg.test @@ -7,6 +7,7 @@ ; CHECK-DAG: USAGE: rc [options] ; CHECK-DAG: OPTIONS: ; CHECK-NEXT: /? Display this help and exit. +; CHECK-NEXT: /dry-run Don't compile the input; only try to parse it. ; CHECK-NEXT: /D Define a symbol for the C preprocessor. ; CHECK-NEXT: /FO Change the output file location. ; CHECK-NEXT: /H Display this help and exit. Index: llvm/test/tools/llvm-rc/output-boundary-values.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/output-boundary-values.test @@ -0,0 +1,8 @@ +; RUN: llvm-rc /FO %t %p/Inputs/output-boundary-values.rc +; RUN: diff %t %p/Inputs/empty.res + +; RUN: not llvm-rc /FO %t %p/Inputs/output-boundary-values-error-1.rc 2>&1 | FileCheck %s --check-prefix ERR1 +; ERR1: llvm-rc: Primary language ID (1024) does not fit in 10 bits. + +; RUN: not llvm-rc /FO %t %p/Inputs/output-boundary-values-error-2.rc 2>&1 | FileCheck %s --check-prefix ERR2 +; ERR2: llvm-rc: Sublanguage ID (64) does not fit in 6 bits. Index: llvm/test/tools/llvm-rc/parser-expr.test =================================================================== --- llvm/test/tools/llvm-rc/parser-expr.test +++ llvm/test/tools/llvm-rc/parser-expr.test @@ -1,4 +1,4 @@ -; RUN: llvm-rc /V %p/Inputs/parser-expr.rc | FileCheck %s +; RUN: llvm-rc /dry-run /V %p/Inputs/parser-expr.rc | FileCheck %s ; CHECK: Language: 5, Sublanguage: 1 ; CHECK-NEXT: Language: 3, Sublanguage: 2 @@ -17,36 +17,36 @@ ; CHECK-NEXT: Language: 5, Sublanguage: 7 -; RUN: not llvm-rc /V %p/Inputs/parser-expr-bad-binary-1.rc 2>&1 | FileCheck %s --check-prefix BINARY1 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-expr-bad-binary-1.rc 2>&1 | FileCheck %s --check-prefix BINARY1 ; BINARY1: llvm-rc: Error parsing file: expected '-', '~', integer or '(', got & -; RUN: not llvm-rc /V %p/Inputs/parser-expr-bad-binary-2.rc 2>&1 | FileCheck %s --check-prefix BINARY2 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-expr-bad-binary-2.rc 2>&1 | FileCheck %s --check-prefix BINARY2 ; BINARY2: llvm-rc: Error parsing file: expected '-', '~', integer or '(', got | -; RUN: not llvm-rc /V %p/Inputs/parser-expr-bad-binary-3.rc 2>&1 | FileCheck %s --check-prefix BINARY3 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-expr-bad-binary-3.rc 2>&1 | FileCheck %s --check-prefix BINARY3 ; BINARY3: llvm-rc: Error parsing file: expected '-', '~', integer or '(', got + -; RUN: not llvm-rc /V %p/Inputs/parser-expr-bad-unary.rc 2>&1 | FileCheck %s --check-prefix UNARY +; RUN: not llvm-rc /dry-run %p/Inputs/parser-expr-bad-unary.rc 2>&1 | FileCheck %s --check-prefix UNARY ; UNARY: llvm-rc: Error parsing file: expected ',', got ~ -; RUN: not llvm-rc /V %p/Inputs/parser-expr-unbalanced-1.rc 2>&1 | FileCheck %s --check-prefix UNBALANCED1 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-expr-unbalanced-1.rc 2>&1 | FileCheck %s --check-prefix UNBALANCED1 ; UNBALANCED1: llvm-rc: Error parsing file: expected ')', got , -; RUN: not llvm-rc /V %p/Inputs/parser-expr-unbalanced-2.rc 2>&1 | FileCheck %s --check-prefix UNBALANCED2 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-expr-unbalanced-2.rc 2>&1 | FileCheck %s --check-prefix UNBALANCED2 ; UNBALANCED2: llvm-rc: Error parsing file: expected ',', got ) -; RUN: not llvm-rc /V %p/Inputs/parser-expr-unbalanced-3.rc 2>&1 | FileCheck %s --check-prefix UNBALANCED3 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-expr-unbalanced-3.rc 2>&1 | FileCheck %s --check-prefix UNBALANCED3 ; UNBALANCED3: llvm-rc: Error parsing file: expected ',', got ) Index: llvm/test/tools/llvm-rc/parser.test =================================================================== --- llvm/test/tools/llvm-rc/parser.test +++ llvm/test/tools/llvm-rc/parser.test @@ -1,4 +1,4 @@ -; RUN: llvm-rc /V %p/Inputs/parser-correct-everything.rc | FileCheck %s --check-prefix PGOOD +; RUN: llvm-rc /dry-run /V %p/Inputs/parser-correct-everything.rc | FileCheck %s --check-prefix PGOOD ; PGOOD: Icon (meh): "hello.bmp" ; PGOOD-NEXT: Icon (Icon): "Icon" @@ -100,151 +100,151 @@ -; RUN: not llvm-rc /V %p/Inputs/parser-stringtable-no-string.rc 2>&1 | FileCheck %s --check-prefix PSTRINGTABLE1 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-stringtable-no-string.rc 2>&1 | FileCheck %s --check-prefix PSTRINGTABLE1 ; PSTRINGTABLE1: llvm-rc: Error parsing file: expected string, got } -; RUN: not llvm-rc /V %p/Inputs/parser-stringtable-weird-option.rc 2>&1 | FileCheck %s --check-prefix PSTRINGTABLE2 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-stringtable-weird-option.rc 2>&1 | FileCheck %s --check-prefix PSTRINGTABLE2 ; PSTRINGTABLE2: llvm-rc: Error parsing file: expected optional statement type, BEGIN or '{', got NONSENSETYPE -; RUN: not llvm-rc /V %p/Inputs/parser-eof.rc 2>&1 | FileCheck %s --check-prefix PEOF +; RUN: not llvm-rc /dry-run %p/Inputs/parser-eof.rc 2>&1 | FileCheck %s --check-prefix PEOF ; PEOF: llvm-rc: Error parsing file: expected '-', '~', integer or '(', got -; RUN: not llvm-rc /V %p/Inputs/parser-no-characteristics-arg.rc 2>&1 | FileCheck %s --check-prefix PCHARACTERISTICS1 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-no-characteristics-arg.rc 2>&1 | FileCheck %s --check-prefix PCHARACTERISTICS1 ; PCHARACTERISTICS1: llvm-rc: Error parsing file: expected '-', '~', integer or '(', got BEGIN -; RUN: not llvm-rc /V %p/Inputs/parser-nonsense-token.rc 2>&1 | FileCheck %s --check-prefix PNONSENSE1 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-nonsense-token.rc 2>&1 | FileCheck %s --check-prefix PNONSENSE1 ; PNONSENSE1: llvm-rc: Error parsing file: expected int or identifier, got & -; RUN: not llvm-rc /V %p/Inputs/parser-nonsense-type.rc 2>&1 | FileCheck %s --check-prefix PNONSENSE2 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-nonsense-type.rc 2>&1 | FileCheck %s --check-prefix PNONSENSE2 ; PNONSENSE2: llvm-rc: Error parsing file: expected filename, '{' or BEGIN, got -; RUN: not llvm-rc /V %p/Inputs/parser-nonsense-type-eof.rc 2>&1 | FileCheck %s --check-prefix PNONSENSE3 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-nonsense-type-eof.rc 2>&1 | FileCheck %s --check-prefix PNONSENSE3 ; PNONSENSE3: llvm-rc: Error parsing file: expected int or identifier, got -; RUN: not llvm-rc /V %p/Inputs/parser-language-no-comma.rc 2>&1 | FileCheck %s --check-prefix PLANGUAGE1 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-language-no-comma.rc 2>&1 | FileCheck %s --check-prefix PLANGUAGE1 ; PLANGUAGE1: llvm-rc: Error parsing file: expected ',', got 7 -; RUN: not llvm-rc /V %p/Inputs/parser-language-too-many-commas.rc 2>&1 | FileCheck %s --check-prefix PLANGUAGE2 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-language-too-many-commas.rc 2>&1 | FileCheck %s --check-prefix PLANGUAGE2 ; PLANGUAGE2: llvm-rc: Error parsing file: expected '-', '~', integer or '(', got , -; RUN: not llvm-rc /V %p/Inputs/parser-html-bad-string.rc 2>&1 | FileCheck %s --check-prefix PHTML1 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-html-bad-string.rc 2>&1 | FileCheck %s --check-prefix PHTML1 ; PHTML1: llvm-rc: Error parsing file: expected string, got ThisPassesInTheOriginalToolButDocSaysItShouldBeQuoted -; RUN: not llvm-rc /V %p/Inputs/parser-html-extra-comma.rc 2>&1 | FileCheck %s --check-prefix PHTML2 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-html-extra-comma.rc 2>&1 | FileCheck %s --check-prefix PHTML2 ; PHTML2: llvm-rc: Error parsing file: expected string, got , -; RUN: not llvm-rc /V %p/Inputs/parser-accelerators-bad-flag.rc 2>&1 | FileCheck %s --check-prefix PACCELERATORS1 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-accelerators-bad-flag.rc 2>&1 | FileCheck %s --check-prefix PACCELERATORS1 ; PACCELERATORS1: llvm-rc: Error parsing file: expected ASCII/VIRTKEY/NOINVERT/ALT/SHIFT/CONTROL, got HELLO -; RUN: not llvm-rc /V %p/Inputs/parser-accelerators-bad-int-or-string.rc 2>&1 | FileCheck %s --check-prefix PACCELERATORS2 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-accelerators-bad-int-or-string.rc 2>&1 | FileCheck %s --check-prefix PACCELERATORS2 ; PACCELERATORS2: llvm-rc: Error parsing file: expected int or string, got NotIntOrString -; RUN: not llvm-rc /V %p/Inputs/parser-accelerators-no-comma.rc 2>&1 | FileCheck %s --check-prefix PACCELERATORS3 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-accelerators-no-comma.rc 2>&1 | FileCheck %s --check-prefix PACCELERATORS3 ; PACCELERATORS3: llvm-rc: Error parsing file: expected int or string, got CONTROL -; RUN: not llvm-rc /V %p/Inputs/parser-accelerators-no-comma-2.rc 2>&1 | FileCheck %s --check-prefix PACCELERATORS4 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-accelerators-no-comma-2.rc 2>&1 | FileCheck %s --check-prefix PACCELERATORS4 ; PACCELERATORS4: llvm-rc: Error parsing file: expected ',', got 10 -; RUN: not llvm-rc /V %p/Inputs/parser-menu-bad-id.rc 2>&1 | FileCheck %s --check-prefix PMENU1 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-menu-bad-id.rc 2>&1 | FileCheck %s --check-prefix PMENU1 ; PMENU1: llvm-rc: Error parsing file: expected '-', '~', integer or '(', got A -; RUN: not llvm-rc /V %p/Inputs/parser-menu-bad-flag.rc 2>&1 | FileCheck %s --check-prefix PMENU2 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-menu-bad-flag.rc 2>&1 | FileCheck %s --check-prefix PMENU2 ; PMENU2: llvm-rc: Error parsing file: expected CHECKED/GRAYED/HELP/INACTIVE/MENUBARBREAK/MENUBREAK, got ERRONEOUS -; RUN: not llvm-rc /V %p/Inputs/parser-menu-missing-block.rc 2>&1 | FileCheck %s --check-prefix PMENU3 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-menu-missing-block.rc 2>&1 | FileCheck %s --check-prefix PMENU3 ; PMENU3: llvm-rc: Error parsing file: expected '{', got POPUP -; RUN: not llvm-rc /V %p/Inputs/parser-menu-misspelled-separator.rc 2>&1 | FileCheck %s --check-prefix PMENU4 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-menu-misspelled-separator.rc 2>&1 | FileCheck %s --check-prefix PMENU4 ; PMENU4: llvm-rc: Error parsing file: expected SEPARATOR or string, got NOTSEPARATOR -; RUN: not llvm-rc /V %p/Inputs/parser-dialog-cant-give-helpid.rc 2>&1 | FileCheck %s --check-prefix PDIALOG1 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-dialog-cant-give-helpid.rc 2>&1 | FileCheck %s --check-prefix PDIALOG1 ; PDIALOG1: llvm-rc: Error parsing file: expected identifier, got , -; RUN: not llvm-rc /V %p/Inputs/parser-dialog-too-few-args.rc 2>&1 | FileCheck %s --check-prefix PDIALOG2 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-dialog-too-few-args.rc 2>&1 | FileCheck %s --check-prefix PDIALOG2 ; PDIALOG2: llvm-rc: Error parsing file: expected ',', got } -; RUN: not llvm-rc /V %p/Inputs/parser-dialog-too-many-args.rc 2>&1 | FileCheck %s --check-prefix PDIALOG3 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-dialog-too-many-args.rc 2>&1 | FileCheck %s --check-prefix PDIALOG3 ; PDIALOG3: llvm-rc: Error parsing file: expected identifier, got , -; RUN: not llvm-rc /V %p/Inputs/parser-dialog-unknown-type.rc 2>&1 | FileCheck %s --check-prefix PDIALOG4 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-dialog-unknown-type.rc 2>&1 | FileCheck %s --check-prefix PDIALOG4 ; PDIALOG4: llvm-rc: Error parsing file: expected control type, END or '}', got UNKNOWN -; RUN: not llvm-rc /V %p/Inputs/parser-dialog-unnecessary-string.rc 2>&1 | FileCheck %s --check-prefix PDIALOG5 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-dialog-unnecessary-string.rc 2>&1 | FileCheck %s --check-prefix PDIALOG5 ; PDIALOG5: llvm-rc: Error parsing file: expected '-', '~', integer or '(', got "This shouldn't be here" -; RUN: not llvm-rc /V %p/Inputs/parser-versioninfo-wrong-fixed.rc 2>&1 | FileCheck %s --check-prefix PVERSIONINFO1 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-versioninfo-wrong-fixed.rc 2>&1 | FileCheck %s --check-prefix PVERSIONINFO1 ; PVERSIONINFO1: llvm-rc: Error parsing file: expected fixed VERSIONINFO statement type, got WEIRDFIXED -; RUN: not llvm-rc /V %p/Inputs/parser-versioninfo-named-main-block.rc 2>&1 | FileCheck %s --check-prefix PVERSIONINFO2 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-versioninfo-named-main-block.rc 2>&1 | FileCheck %s --check-prefix PVERSIONINFO2 ; PVERSIONINFO2: llvm-rc: Error parsing file: expected fixed VERSIONINFO statement type, got BLOCK -; RUN: not llvm-rc /V %p/Inputs/parser-versioninfo-unnamed-inner-block.rc 2>&1 | FileCheck %s --check-prefix PVERSIONINFO3 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-versioninfo-unnamed-inner-block.rc 2>&1 | FileCheck %s --check-prefix PVERSIONINFO3 ; PVERSIONINFO3: llvm-rc: Error parsing file: expected string, got { -; RUN: not llvm-rc /V %p/Inputs/parser-versioninfo-unnamed-value.rc 2>&1 | FileCheck %s --check-prefix PVERSIONINFO4 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-versioninfo-unnamed-value.rc 2>&1 | FileCheck %s --check-prefix PVERSIONINFO4 ; PVERSIONINFO4: llvm-rc: Error parsing file: expected string, got END -; RUN: not llvm-rc /V %p/Inputs/parser-versioninfo-bad-type.rc 2>&1 | FileCheck %s --check-prefix PVERSIONINFO5 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-versioninfo-bad-type.rc 2>&1 | FileCheck %s --check-prefix PVERSIONINFO5 ; PVERSIONINFO5: llvm-rc: Error parsing file: expected BLOCK or VALUE, got INCORRECT -; RUN: not llvm-rc /V %p/Inputs/parser-user-invalid-contents.rc 2>&1 | FileCheck %s --check-prefix PUSER1 +; RUN: not llvm-rc /dry-run %p/Inputs/parser-user-invalid-contents.rc 2>&1 | FileCheck %s --check-prefix PUSER1 ; PUSER1: llvm-rc: Error parsing file: expected int or string, got InvalidToken Index: llvm/test/tools/llvm-rc/tag-html.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/tag-html.test @@ -0,0 +1,6 @@ +; RUN: rm -rf %t +; RUN: mkdir %t +; RUN: cd %t +; RUN: cp %p/Inputs/webpage*.html . +; RUN: llvm-rc /FO %t/tag-html.res %p/Inputs/tag-html.rc +; RUN: diff %t/tag-html.res %p/Inputs/tag-html.res Index: llvm/tools/llvm-rc/CMakeLists.txt =================================================================== --- llvm/tools/llvm-rc/CMakeLists.txt +++ llvm/tools/llvm-rc/CMakeLists.txt @@ -1,4 +1,5 @@ set(LLVM_LINK_COMPONENTS + Object Option Support ) Index: llvm/tools/llvm-rc/Opts.td =================================================================== --- llvm/tools/llvm-rc/Opts.td +++ llvm/tools/llvm-rc/Opts.td @@ -32,6 +32,9 @@ Alias, HelpText<"Display this help and exit.">; +def DRY_RUN : Flag<[ "/", "-" ], "dry-run">, + HelpText<"Don't compile the input; only try to parse it.">; + // 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. Index: llvm/tools/llvm-rc/ResourceScriptParser.cpp =================================================================== --- llvm/tools/llvm-rc/ResourceScriptParser.cpp +++ llvm/tools/llvm-rc/ResourceScriptParser.cpp @@ -355,7 +355,7 @@ if (TypeToken->equals_lower("STYLE")) return parseStyleStmt(); } - + return getExpectedError("optional statement type, BEGIN or '{'", /* IsAlreadyRead = */ true); } @@ -525,7 +525,7 @@ List.addDefinition(make_unique()); continue; } - + return getExpectedError("SEPARATOR or string", true); } @@ -615,9 +615,8 @@ Values.push_back(*ValueResult); } return make_unique(*KeyResult, std::move(Values)); - } - + return getExpectedError("BLOCK or VALUE", true); } @@ -641,7 +640,7 @@ Result.setValue(*TypeResult, *ArgsResult); continue; } - + // Other ones take exactly one integer. ASSIGN_OR_RETURN(ArgResult, readInt()); Result.setValue(*TypeResult, *ArgResult); Index: llvm/tools/llvm-rc/ResourceScriptStmt.h =================================================================== --- llvm/tools/llvm-rc/ResourceScriptStmt.h +++ llvm/tools/llvm-rc/ResourceScriptStmt.h @@ -49,13 +49,44 @@ return !IsInt && Data.String.equals_lower(Str); } + bool isInt() const { return IsInt; } + + uint32_t getInt() const { + assert(IsInt); + return Data.Int; + } + + StringRef getString() const { + assert(!IsInt); + return Data.String; + } + friend raw_ostream &operator<<(raw_ostream &, const IntOrString &); }; +class VisitorContext { +public: + std::unique_ptr FS; + uint16_t LanguageInfo; + uint32_t Characteristics; + uint32_t VersionInfo; + + VisitorContext() : LanguageInfo(0), Characteristics(0), VersionInfo(0) {} + + void setStream(std::unique_ptr Stream) { + FS = std::move(Stream); + } +}; + +class OptionalStmtList; + // Base resource. All the resources should derive from this base. class RCResource { protected: IntOrString ResName; + virtual Error dumpResource(VisitorContext &Ctx) const { + llvm_unreachable("This cannot dump to .res file."); + } public: RCResource() = default; @@ -63,23 +94,51 @@ void setName(const IntOrString &Name) { ResName = Name; } virtual raw_ostream &log(raw_ostream &OS) const { return OS << "Base statement\n"; - }; + } + + virtual Error visitResource(VisitorContext &Ctx); + + virtual IntOrString getResourceType() const { + llvm_unreachable("This cannot be called on objects without IDs."); + } + // By default, memory flags are DISCARDABLE | PURE | MOVEABLE. + virtual uint16_t getMemoryFlags() const { return 0x1030; } + virtual const OptionalStmtList getOptionalStatements() const; virtual ~RCResource() {} }; +// 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: + raw_ostream &log(raw_ostream &OS) const override { + return OS << "Null resource\n"; + } + Error dumpResource(VisitorContext &Ctx) const override { + return Error::success(); + } + IntOrString getResourceType() const override { return 0; } + uint16_t getMemoryFlags() const override { return 0; } +}; + // Optional statement base. All such statements should derive from this base. -class OptionalStmt : public RCResource {}; +class OptionalStmt : public RCResource { +public: + virtual Error apply(VisitorContext &Ctx) const = 0; +}; class OptionalStmtList : public OptionalStmt { std::vector> Statements; public: OptionalStmtList() {} - virtual raw_ostream &log(raw_ostream &OS) const; + raw_ostream &log(raw_ostream &OS) const override; void addStmt(std::unique_ptr Stmt) { Statements.push_back(std::move(Stmt)); } + + Error apply(VisitorContext &Ctx) const override; }; // LANGUAGE statement. It can occur both as a top-level statement (in such @@ -95,6 +154,12 @@ 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 visitResource(VisitorContext &Ctx) override { return apply(Ctx); } + + Error apply(VisitorContext &Ctx) const override; }; // ACCELERATORS resource. Defines a named table of accelerators for the app. @@ -126,7 +191,9 @@ void addAccelerator(IntOrString Event, uint32_t Id, uint8_t Flags) { Accelerators.push_back(Accelerator{Event, Id, Flags}); } + raw_ostream &log(raw_ostream &) const override; + IntOrString getResourceType() const override { return 9; } private: std::vector Accelerators; @@ -142,6 +209,7 @@ public: CursorResource(StringRef Location) : CursorLoc(Location) {} raw_ostream &log(raw_ostream &) const override; + IntOrString getResourceType() const override { return 21; } }; // ICON resource. Represents a single ".ico" file containing a group of icons. @@ -153,6 +221,7 @@ public: IconResource(StringRef Location) : IconLoc(Location) {} raw_ostream &log(raw_ostream &) const override; + IntOrString getResourceType() const override { return 22; } }; // HTML resource. Represents a local webpage that is to be embedded into the @@ -166,6 +235,10 @@ public: HTMLResource(StringRef Location) : HTMLLoc(Location) {} raw_ostream &log(raw_ostream &) const override; + Error dumpResource(VisitorContext &Ctx) const override; + IntOrString getResourceType() const override { return 23; } + // Curiously, file resources don't have DISCARDABLE flag set. + uint16_t getMemoryFlags() const override { return 0x30; } }; // -- MENU resource and its helper classes -- @@ -252,6 +325,7 @@ MenuResource(OptionalStmtList &&OptStmts, MenuDefinitionList &&Items) : OptStatements(std::move(OptStmts)), Elements(std::move(Items)) {} raw_ostream &log(raw_ostream &) const override; + IntOrString getResourceType() const override { return 4; } }; // STRINGTABLE resource. Contains a list of strings, each having its unique ID. @@ -267,6 +341,7 @@ void addString(uint32_t ID, StringRef String) { Table.emplace_back(ID, String); } + raw_ostream &log(raw_ostream &) const override; }; @@ -317,6 +392,7 @@ void addControl(Control &&Ctl) { Controls.push_back(std::move(Ctl)); } raw_ostream &log(raw_ostream &) const override; + IntOrString getResourceType() const override { return 5; } }; // User-defined resource. It is either: @@ -335,6 +411,7 @@ : Type(ResourceType), Contents(std::move(Data)), IsFileResource(false) {} raw_ostream &log(raw_ostream &) const override; + IntOrString getResourceType() const override { return Type; } }; // -- VERSIONINFO resource and its helper classes -- @@ -418,6 +495,7 @@ : MainBlock(std::move(TopLevelBlock)), FixedData(std::move(FixedInfo)) {} raw_ostream &log(raw_ostream &) const override; + IntOrString getResourceType() const override { return 16; } }; // CHARACTERISTICS optional statement. @@ -429,6 +507,10 @@ public: CharacteristicsStmt(uint32_t Characteristic) : Value(Characteristic) {} raw_ostream &log(raw_ostream &) const override; + Error apply(VisitorContext &Ctx) const override { + Ctx.Characteristics = Value; + return Error::success(); + } }; // VERSION optional statement. @@ -440,6 +522,10 @@ public: VersionStmt(uint32_t Version) : Value(Version) {} raw_ostream &log(raw_ostream &) const override; + Error apply(VisitorContext &Ctx) const override { + Ctx.VersionInfo = Value; + return Error::success(); + } }; // CAPTION optional statement. @@ -451,6 +537,9 @@ public: CaptionStmt(StringRef Caption) : Value(Caption) {} raw_ostream &log(raw_ostream &) const override; + Error apply(VisitorContext &Ctx) const override { + llvm_unreachable("Not implemented yet"); + } }; // FONT optional statement. @@ -467,6 +556,9 @@ FontStmt(uint32_t FontSize, StringRef FontName) : Size(FontSize), Typeface(FontName) {} raw_ostream &log(raw_ostream &) const override; + Error apply(VisitorContext &Ctx) const override { + llvm_unreachable("Not implemented yet"); + } }; // STYLE optional statement. @@ -478,6 +570,9 @@ public: StyleStmt(uint32_t Style) : Value(Style) {} raw_ostream &log(raw_ostream &) const override; + Error apply(VisitorContext &Ctx) const override { + llvm_unreachable("Not implemented yet"); + } }; } // namespace rc Index: llvm/tools/llvm-rc/ResourceScriptStmt.cpp =================================================================== --- llvm/tools/llvm-rc/ResourceScriptStmt.cpp +++ llvm/tools/llvm-rc/ResourceScriptStmt.cpp @@ -14,9 +14,129 @@ #include "ResourceScriptStmt.h" +#include "llvm/Object/WindowsResource.h" +#include "llvm/Support/EndianStream.h" +#include "llvm/Support/Error.h" + +#include + +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 { +static Error checkNumberFits(uint32_t Number, size_t MaxBits, Twine FieldName) { + assert(1 <= MaxBits && MaxBits <= 32); + if (!(Number >> MaxBits)) + return Error::success(); + return make_error( + FieldName + " (" + Twine(Number) + ") does not fit in " + Twine(MaxBits) + + " bits.", + std::make_error_code(std::errc::value_too_large)); +} + +template +static Error checkNumberFits(uint32_t Number, Twine FieldName) { + return checkNumberFits(Number, sizeof(FitType) * 8, FieldName); +} + +static Error checkNumberFits(IntOrString Value, Twine FieldName) { + if (!Value.isInt()) + return Error::success(); + return checkNumberFits(Value.getInt(), FieldName); +} + +// Parses a string/identifier and returns a processed version of it. +// For now, it only strips the string boundaries, but TODO: +// * Squash "" to a single ". +// * Replace the escape sequences with their processed version. +// For identifiers, this is no-op. +static std::string processString(StringRef Str) { + // Identifiers won't have " signs inside. + if (!Str.contains('"')) + return Str; + + // Just take the contents of the string, checking if it's been marked long. + LLVM_ATTRIBUTE_UNUSED bool 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 Str; +} + +class RCWriter { + raw_fd_ostream &FS; + + template void writeImpl(const T &Value) { + FS.write((const char *)&Value, sizeof(T)); + } + + void writeImpl(StringRef Str) { + // Assume for now that we deal with ASCII characters. + for (char Ch : processString(Str)) + writeImpl(ulittle16_t(Ch)); + writeImpl(0); + } + +public: + RCWriter(raw_fd_ostream &Stream) : FS(Stream) {} + + void 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) + writeImpl(0); + } + + template uint64_t write(const T &Value) { + uint64_t Location = tell(); + writeImpl(Value); + return Location; + } + + template void writeAt(const T &Value, uint64_t Loc) { + FS.pwrite((const char *)&Value, sizeof(T), Loc); + } + + void writeUppercase(StringRef Str) { + std::string Upper = Str.upper(); + writeImpl(StringRef(Upper)); + } + + void writeUppercase(const IntOrString &Value) { + if (!Value.isInt()) + return writeUppercase(Value.getString()); + + writeImpl(ulittle16_t(0xFFFF)); + writeImpl(ulittle16_t(Value.getInt())); + } + + Error copyFile(std::string Filename) { + // Filename path should be relative to the current working directory. + ErrorOr> File = + MemoryBuffer::getFile(Filename, -1, false); + if (!File) + return make_error("Error opening file '" + Filename + + "': " + File.getError().message(), + File.getError()); + FS << (*File)->getBuffer(); + return Error::success(); + } + + uint64_t tell() const { return FS.tell(); } +}; + raw_ostream &operator<<(raw_ostream &OS, const IntOrString &Item) { if (Item.IsInt) return OS << Item.Data.Int; @@ -24,6 +144,42 @@ return OS << Item.Data.String; } +Error RCResource::visitResource(VisitorContext &Ctx) { + assert(Ctx.FS && "No output stream provided."); + RCWriter Writer(*Ctx.FS); + // We don't know the sizes yet. + object::WinResHeaderPrefix HeaderPrefix{ulittle32_t(0U), ulittle32_t(0U)}; + uint64_t HeaderLoc = Writer.write(HeaderPrefix); + + auto ResType = getResourceType(); + RETURN_IF_ERROR(checkNumberFits(ResType, "Resource type")); + RETURN_IF_ERROR(checkNumberFits(ResName, "Resource ID")); + Writer.writeUppercase(ResType); + Writer.writeUppercase(ResName); + + Writer.padStream(sizeof(uint32_t)); + object::WinResHeaderSuffix HeaderSuffix{ + ulittle32_t(0U), // DataVersion + ulittle16_t(getMemoryFlags()), ulittle16_t(Ctx.LanguageInfo), + ulittle32_t(Ctx.VersionInfo), ulittle32_t(Ctx.Characteristics)}; + Writer.write(HeaderSuffix); + + uint64_t DataLoc = Writer.tell(); + RETURN_IF_ERROR(dumpResource(Ctx)); + + // Update the sizes. + HeaderPrefix.DataSize = Writer.tell() - DataLoc; + HeaderPrefix.HeaderSize = DataLoc - HeaderLoc; + Writer.writeAt(HeaderPrefix, HeaderLoc); + Writer.padStream(sizeof(uint32_t)); + + return Error::success(); +} + +const OptionalStmtList RCResource::getOptionalStatements() const { + return OptionalStmtList(); +} + raw_ostream &OptionalStmtList::log(raw_ostream &OS) const { for (const auto &Stmt : Statements) { OS << " Option: "; @@ -32,10 +188,23 @@ return OS; } +Error OptionalStmtList::apply(VisitorContext &Ctx) const { + for (auto &Stmt : Statements) + RETURN_IF_ERROR(Stmt->apply(Ctx)); + return Error::success(); +} + raw_ostream &LanguageResource::log(raw_ostream &OS) const { return OS << "Language: " << Lang << ", Sublanguage: " << SubLang << "\n"; } +Error LanguageResource::apply(VisitorContext &Ctx) const { + RETURN_IF_ERROR(checkNumberFits(Lang, 10, "Primary language ID")); + RETURN_IF_ERROR(checkNumberFits(SubLang, 6, "Sublanguage ID")); + Ctx.LanguageInfo = Lang | (SubLang << 10); + return Error::success(); +} + StringRef AcceleratorsResource::Accelerator::OptionsStr [AcceleratorsResource::Accelerator::NumFlags] = { "ASCII", "VIRTKEY", "NOINVERT", "ALT", "SHIFT", "CONTROL"}; @@ -65,6 +234,11 @@ return OS << "HTML (" << ResName << "): " << HTMLLoc << "\n"; } +Error HTMLResource::dumpResource(VisitorContext &Ctx) const { + RCWriter Writer(*Ctx.FS); + return Writer.copyFile(processString(HTMLLoc)); +} + StringRef MenuDefinition::OptionsStr[MenuDefinition::NumFlags] = { "CHECKED", "GRAYED", "HELP", "INACTIVE", "MENUBARBREAK", "MENUBREAK"}; Index: llvm/tools/llvm-rc/llvm-rc.cpp =================================================================== --- llvm/tools/llvm-rc/llvm-rc.cpp +++ llvm/tools/llvm-rc/llvm-rc.cpp @@ -12,12 +12,13 @@ // //===----------------------------------------------------------------------===// -#include "ResourceScriptToken.h" #include "ResourceScriptParser.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/ManagedStatic.h" #include "llvm/Support/PrettyStackTrace.h" #include "llvm/Support/Process.h" @@ -27,6 +28,7 @@ #include using namespace llvm; +using namespace llvm::rc; namespace { @@ -97,18 +99,16 @@ const bool BeVerbose = InputArgs.hasArg(OPT_VERBOSE); - std::vector InArgsInfo = InputArgs.getAllArgValues(OPT_INPUT); - if (InArgsInfo.size() != 1) { + auto InArgsInfo = InputArgs.getAllArgValues(OPT_INPUT); + if (InArgsInfo.size() != 1) fatalError("Exactly one input file should be provided."); - } // Read and tokenize the input file. const Twine &Filename = InArgsInfo[0]; ErrorOr> File = MemoryBuffer::getFile(Filename); - if (!File) { - fatalError("Error opening file '" + Filename + + if (!File) + fatalError("Error opening input file '" + Filename + "': " + File.getError().message()); - } std::unique_ptr FileContents = std::move(*File); StringRef Contents = FileContents->getBuffer(); @@ -132,13 +132,42 @@ outs() << "\n"; } + outs().flush(); + } + + bool IsDryRun = InputArgs.hasArg(OPT_DRY_RUN); + + VisitorContext Ctx; + + if (!IsDryRun) { + auto OutArgsInfo = InputArgs.getAllArgValues(OPT_FILEOUT); + if (OutArgsInfo.size() != 1) + fatalError( + "Exactly one output file should be provided (using /FO flag)."); + + std::error_code EC; + auto FOut = make_unique(OutArgsInfo[0], EC, sys::fs::F_RW); + if (EC) + fatalError("Error opening output file '" + OutArgsInfo[0] + + "': " + EC.message()); + Ctx.setStream(std::move(FOut)); + + // Put an empty resource - .res file header. + ExitOnErr(NullResource().visitResource(Ctx)); + + // Set the default language; choose en-US arbitrarily. + ExitOnErr(LanguageResource(0x09, /* LANG_ENGLISH */ + 0x01 /* SUBLANG_ENGLISH_US */) + .visitResource(Ctx)); } - rc::RCParser Parser{std::move(Tokens)}; + RCParser Parser{std::move(Tokens)}; while (!Parser.isEof()) { auto Resource = ExitOnErr(Parser.parseSingleResource()); if (BeVerbose) Resource->log(outs()); + if (!IsDryRun) + ExitOnErr(Resource->visitResource(Ctx)); } return 0;