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"
+Kittens 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/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,127 @@
#include "ResourceScriptStmt.h"
+#include "llvm/Support/EndianStream.h"
+#include "llvm/Support/Error.h"
+
+#include
+
+// 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 {
+ support::endian::Writer Writer;
+ raw_fd_ostream &FS;
+
+ template void writeImpl(T Value) {
+ padStream(sizeof(T));
+ Writer.write(Value);
+ }
+
+ void writeImpl(StringRef Str) {
+ for (char Ch : processString(Str))
+ Writer.write(Ch);
+ }
+
+public:
+ RCWriter(raw_fd_ostream &Stream) : Writer(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(T Value) {
+ uint64_t Location = tell();
+ writeImpl(Value);
+ return Location;
+ }
+
+ template void writeAt(T Value, uint64_t Loc) {
+ Value = support::endian::byte_swap(Value);
+ 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());
+
+ Writer.write(0xFFFF);
+ Writer.write(Value.getInt());
+ }
+
+ Error copyFile(std::string Filename) {
+ // Filename path should be relative to the current working directory.
+ ErrorOr> File =
+ MemoryBuffer::getFile(Filename);
+ 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 +142,34 @@
return OS << Item.Data.String;
}
+Error RCResource::visitResource(VisitorContext &Ctx) {
+ assert(Ctx.FS && "No output stream provided.");
+ RCWriter Writer(*Ctx.FS);
+ uint64_t ResSizeLoc = Writer.write(0);
+ uint64_t HeaderSizeLoc = Writer.write(0);
+ RETURN_IF_ERROR(checkNumberFits(ResName, "Resource ID"));
+ Writer.writeUppercase(getResourceType());
+ Writer.writeUppercase(ResName);
+ Writer.write(0); // DataVersion
+ Writer.write(getMemoryFlags());
+ Writer.write(Ctx.LanguageInfo);
+ Writer.write(Ctx.VersionInfo);
+ Writer.write(Ctx.Characteristics);
+
+ uint64_t DataLoc = Writer.tell();
+ RETURN_IF_ERROR(dumpResource(Ctx));
+
+ Writer.writeAt(Writer.tell() - DataLoc, ResSizeLoc);
+ Writer.writeAt(DataLoc - ResSizeLoc, HeaderSizeLoc);
+ 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 +178,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 +224,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;