Index: llvm/test/tools/llvm-rc/Inputs/tag-html-wrong.rc
===================================================================
--- /dev/null
+++ llvm/test/tools/llvm-rc/Inputs/tag-html-wrong.rc
@@ -0,0 +1 @@
+1 HTML "some-really-nonexistent-file.html"
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/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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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 /V %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,41 @@
+; RUN: rm -rf %t && mkdir %t && cd %t
+; RUN: cp %p/Inputs/webpage*.html .
+; RUN: llvm-rc /FO %t/tag-html.res %p/Inputs/tag-html.rc
+; RUN: llvm-readobj %t/tag-html.res | FileCheck %s --check-prefix HTML
+
+; HTML: Resource type (int): 23
+; HTML-NEXT: Resource name (int): 100
+; HTML-NEXT: Data version: 0
+; HTML-NEXT: Memory flags: 0x30
+; HTML-NEXT: Language ID: 1033
+; HTML-NEXT: Version (major): 0
+; HTML-NEXT: Version (minor): 0
+; HTML-NEXT: Characteristics: 0
+; HTML-NEXT: Data size: 45
+; HTML-NEXT: Data: (
+; HTML-NEXT: 0000: 3C68746D 6C3E0A20 203C626F 64793E0A |. .|
+; HTML-NEXT: 0010: 20202020 48656C6C 6F210A20 203C2F62 | Hello!. ..|
+; HTML-NEXT: )
+
+; HTML-DAG: Resource type (int): 23
+; HTML-NEXT: Resource name (string): KITTEN
+; HTML-NEXT: Data version: 0
+; HTML-NEXT: Memory flags: 0x30
+; HTML-NEXT: Language ID: 1033
+; HTML-NEXT: Version (major): 0
+; HTML-NEXT: Version (minor): 0
+; HTML-NEXT: Characteristics: 0
+; HTML-NEXT: Data size: 61
+; HTML-NEXT: Data: (
+; HTML-NEXT: 0000: 3C212D2D 2053686F 756C6420 6E6F7420 |..|
+; HTML-NEXT: )
+
+
+; RUN: not llvm-rc /FO %t/tag-html-wrong.res %p/Inputs/tag-html-wrong.rc 2>&1 | FileCheck %s --check-prefix NOFILE
+
+; NOFILE: llvm-rc: Error in HTML statement (ID 1):
+; NOFILE-NEXT: Error opening file 'some-really-nonexistent-file.html':
Index: llvm/tools/llvm-rc/CMakeLists.txt
===================================================================
--- llvm/tools/llvm-rc/CMakeLists.txt
+++ llvm/tools/llvm-rc/CMakeLists.txt
@@ -10,6 +10,7 @@
add_llvm_tool(llvm-rc
llvm-rc.cpp
+ ResourceFileWriter.cpp
ResourceScriptParser.cpp
ResourceScriptStmt.cpp
ResourceScriptToken.cpp
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/ResourceFileWriter.h
===================================================================
--- /dev/null
+++ llvm/tools/llvm-rc/ResourceFileWriter.h
@@ -0,0 +1,89 @@
+//===-- ResourceSerializator.h ----------------------------------*- C++-*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===---------------------------------------------------------------------===//
+//
+// 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 {
+namespace rc {
+
+class ResourceFileWriter : public Visitor {
+public:
+ ResourceFileWriter(std::unique_ptr Stream)
+ : FS(std::move(Stream)) {
+ assert(FS && "Output stream needs to be provided to the serializator");
+ }
+
+ Error visitNullResource(const RCResource *) override;
+ Error visitHTMLResource(const RCResource *) override;
+
+ Error visitLanguageStmt(const LanguageResource *) override;
+
+ struct ObjectInfo {
+ uint16_t LanguageInfo;
+
+ ObjectInfo() : LanguageInfo(0) {}
+ } ObjectData;
+
+private:
+ Error handleError(Error &&Err, const RCResource *Res);
+
+ Error
+ writeResource(const RCResource *Res,
+ Error (ResourceFileWriter::*BodyWriter)(const RCResource *));
+
+ Error writeNullBody(const RCResource *);
+ Error writeHTMLBody(const RCResource *);
+
+ // Output stream handling.
+ std::unique_ptr FS;
+
+ uint64_t tell() const { return FS->tell(); }
+
+ uint64_t writeObject(const ArrayRef Data);
+
+ template uint64_t writeInt(const T &Value) {
+ support::detail::packed_endian_specific_integral
+ Object(Value);
+ return writeObject(Object);
+ }
+
+ template uint64_t writeObject(const T &Value) {
+ return writeObject(ArrayRef(
+ reinterpret_cast(&Value), sizeof(T)));
+ }
+
+ template 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);
+
+ Error appendFile(StringRef Filename);
+
+ void padStream(uint64_t Length);
+};
+
+} // namespace rc
+} // namespace llvm
+
+#endif
Index: llvm/tools/llvm-rc/ResourceFileWriter.cpp
===================================================================
--- /dev/null
+++ llvm/tools/llvm-rc/ResourceFileWriter.cpp
@@ -0,0 +1,241 @@
+//===-- ResourceFileWriter.cpp --------------------------------*- C++-*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===---------------------------------------------------------------------===//
+//
+// 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"
+
+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 createError(Twine Message,
+ std::errc Type = std::errc::invalid_argument) {
+ return make_error(Message, std::make_error_code(Type));
+}
+
+static Error checkNumberFits(uint32_t Number, size_t MaxBits, 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
+static Error checkNumberFits(uint32_t Number, Twine FieldName) {
+ return checkNumberFits(Number, sizeof(FitType) * 8, FieldName);
+}
+
+static Error checkIntOrString(IntOrString Value, Twine FieldName) {
+ if (!Value.isInt())
+ return Error::success();
+ return checkNumberFits(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;
+}
+
+// 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.
+// 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 Error processString(StringRef Str, NullHandlingMethod NullHandler,
+ bool &IsLongString, SmallVectorImpl &Result) {
+ assert(NullHandler == NullHandlingMethod::CutAtNull);
+
+ bool IsString = stripQuotes(Str, IsLongString);
+ convertUTF8ToUTF16String(Str, Result);
+
+ if (!IsString) {
+ // It's an identifier if it's not a string. Make all characters uppercase.
+ for (UTF16 &Ch : Result) {
+ assert(Ch <= 0x7F && "We didn't allow identifiers to be non-ASCII");
+ Ch = toupper(Ch);
+ }
+ return Error::success();
+ }
+
+ // We don't process the string contents. Only cut at '\0'.
+
+ for (size_t Pos = 0; Pos < Result.size(); ++Pos)
+ if (Result[Pos] == '\0')
+ Result.resize(Pos);
+
+ return Error::success();
+}
+
+uint64_t ResourceFileWriter::writeObject(const ArrayRef Data) {
+ uint64_t Result = tell();
+ FS->write((const char *)Data.begin(), Data.size());
+ return Result;
+}
+
+Error ResourceFileWriter::writeCString(StringRef Str, bool WriteTerminator) {
+ SmallVector ProcessedString;
+ bool IsLongString;
+ RETURN_IF_ERROR(processString(Str, NullHandlingMethod::CutAtNull,
+ IsLongString, ProcessedString));
+ for (auto Ch : ProcessedString)
+ writeInt(Ch);
+ if (WriteTerminator)
+ writeInt(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(0xFFFF);
+ writeInt(Value.getInt());
+ return Error::success();
+}
+
+Error ResourceFileWriter::appendFile(StringRef Filename) {
+ bool IsLong;
+ stripQuotes(Filename, IsLong);
+
+ // Filename path should be relative to the current working directory.
+ // FIXME: docs say so, but reality is more complicated, script
+ // location and include paths must be taken into account.
+ 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();
+}
+
+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(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::visitHTMLResource(const RCResource *Res) {
+ return writeResource(Res, &ResourceFileWriter::writeHTMLBody);
+}
+
+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::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));
+
+ padStream(sizeof(uint32_t));
+ object::WinResHeaderSuffix HeaderSuffix{
+ ulittle32_t(0), // DataVersion; seems to always be 0
+ ulittle16_t(Res->getMemoryFlags()), ulittle16_t(ObjectData.LanguageInfo),
+ ulittle32_t(0), // VersionInfo
+ ulittle32_t(0)}; // 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();
+}
+
+Error ResourceFileWriter::writeNullBody(const RCResource *) {
+ return Error::success();
+}
+
+Error ResourceFileWriter::writeHTMLBody(const RCResource *Base) {
+ return appendFile(cast(Base)->HTMLLoc);
+}
+
+} // namespace rc
+} // namespace llvm
Index: llvm/tools/llvm-rc/ResourceScriptStmt.h
===================================================================
--- llvm/tools/llvm-rc/ResourceScriptStmt.h
+++ llvm/tools/llvm-rc/ResourceScriptStmt.h
@@ -15,6 +15,7 @@
#define LLVM_TOOLS_LLVMRC_RESOURCESCRIPTSTMT_H
#include "ResourceScriptToken.h"
+#include "ResourceVisitor.h"
#include "llvm/ADT/StringSet.h"
@@ -49,15 +50,61 @@
return !IsInt && Data.String.equals_lower(Str);
}
+ bool isInt() const { return IsInt; }
+
+ uint32_t 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,
+ RkMenu = 4,
+ RkDialog = 5,
+ RkAccelerators = 9,
+ 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,
+ RkUser
+};
+
+// 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 {
-protected:
+public:
IntOrString ResName;
-public:
RCResource() = default;
RCResource(RCResource &&) = default;
void setName(const IntOrString &Name) { ResName = Name; }
@@ -65,6 +112,37 @@
return OS << "Base statement\n";
};
virtual ~RCResource() {}
+
+ virtual Error visit(Visitor *) const {
+ llvm_unreachable("This is unable to call methods from Visitor base");
+ }
+
+ // By default, memory flags are DISCARDABLE | PURE | MOVEABLE.
+ virtual uint16_t getMemoryFlags() const {
+ 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:
+ 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)"; }
+ uint16_t getMemoryFlags() const override { return 0; }
};
// Optional statement base. All such statements should derive from this base.
@@ -89,12 +167,17 @@
//
// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381019(v=vs.85).aspx
class LanguageResource : public OptionalStmt {
+public:
uint32_t Lang, SubLang;
-public:
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.
@@ -161,11 +244,22 @@
//
// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa966018(v=vs.85).aspx
class HTMLResource : public RCResource {
+public:
StringRef HTMLLoc;
-public:
HTMLResource(StringRef Location) : 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.
+ uint16_t getMemoryFlags() const override { 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 --
Index: llvm/tools/llvm-rc/ResourceVisitor.h
===================================================================
--- /dev/null
+++ llvm/tools/llvm-rc/ResourceVisitor.h
@@ -0,0 +1,39 @@
+//===-- ResourceVisitor.h ---------------------------------------*- C++-*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===---------------------------------------------------------------------===//
+//
+// 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 LanguageResource;
+
+class Visitor {
+public:
+ virtual Error visitNullResource(const RCResource *) = 0;
+ virtual Error visitHTMLResource(const RCResource *) = 0;
+
+ virtual Error visitLanguageStmt(const LanguageResource *) = 0;
+
+ virtual ~Visitor() {}
+};
+
+} // namespace rc
+} // namespace llvm
+
+#endif
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,15 @@
//
//===----------------------------------------------------------------------===//
-#include "ResourceScriptToken.h"
+#include "ResourceFileWriter.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/ManagedStatic.h"
#include "llvm/Support/PrettyStackTrace.h"
#include "llvm/Support/Process.h"
@@ -27,6 +30,7 @@
#include
using namespace llvm;
+using namespace llvm::rc;
namespace {
@@ -134,11 +138,36 @@
}
}
+ std::unique_ptr Visitor;
+ bool IsDryRun = InputArgs.hasArg(OPT_DRY_RUN);
+
+ 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 =
+ llvm::make_unique(OutArgsInfo[0], EC, sys::fs::F_RW);
+ if (EC)
+ fatalError("Error opening output file '" + OutArgsInfo[0] +
+ "': " + EC.message());
+ Visitor = make_unique(std::move(FOut));
+
+ ExitOnErr(NullResource().visit(Visitor.get()));
+
+ // Set the default language; choose en-US arbitrarily.
+ ExitOnErr(LanguageResource(0x09, 0x01).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()));
}
return 0;