[llvm] [llvm-rc] Add support for multiplication and division in expressions (PR #143373)
Martin Storsjö via llvm-commits
llvm-commits at lists.llvm.org
Tue Jun 10 04:27:14 PDT 2025
https://github.com/mstorsjo updated https://github.com/llvm/llvm-project/pull/143373
>From d04c54027c616c61285705007f08a5a8d387feaa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <martin at martin.st>
Date: Mon, 9 Jun 2025 00:11:33 +0300
Subject: [PATCH] [llvm-rc] Add support for multiplication and division in
expressions
This is supported by GNU windres. MS rc.exe does accept these expressions,
but doesn't evalulate them correctly, it only returns the left hand side.
This fixes one aspect of
https://github.com/llvm/llvm-project/issues/143157.
---
llvm/test/tools/llvm-rc/Inputs/parser-expr.rc | 5 ++
llvm/test/tools/llvm-rc/Inputs/tokens.rc | 1 +
llvm/test/tools/llvm-rc/parser-expr.test | 5 ++
llvm/test/tools/llvm-rc/tokenizer.test | 5 ++
llvm/tools/llvm-rc/ResourceScriptParser.cpp | 49 ++++++++++++++-----
llvm/tools/llvm-rc/ResourceScriptParser.h | 1 +
llvm/tools/llvm-rc/ResourceScriptStmt.h | 24 +++++++++
llvm/tools/llvm-rc/ResourceScriptToken.cpp | 12 ++++-
llvm/tools/llvm-rc/ResourceScriptToken.h | 7 ++-
.../tools/llvm-rc/ResourceScriptTokenList.def | 2 +
10 files changed, 97 insertions(+), 14 deletions(-)
diff --git a/llvm/test/tools/llvm-rc/Inputs/parser-expr.rc b/llvm/test/tools/llvm-rc/Inputs/parser-expr.rc
index 8e69c1cd1fa16..2f8e4b2d344a0 100644
--- a/llvm/test/tools/llvm-rc/Inputs/parser-expr.rc
+++ b/llvm/test/tools/llvm-rc/Inputs/parser-expr.rc
@@ -5,6 +5,11 @@ LANGUAGE 1|1&0, 0&0|1
LANGUAGE 3+4-5, 3-4+5
LANGUAGE 1+2|3, 3|1+2
LANGUAGE 6&~5, 6&-8
+LANGUAGE 7/3, 7*3
+LANGUAGE 5/2*2, 5*3/2
+LANGUAGE 1+2*3, (1+2)*3
+LANGUAGE 100/12/5*5, 1+1+1+1*4
+LANGUAGE 9/(1+3), (4+5)/4
LANGUAGE -1, --1
LANGUAGE ----1, -----1
LANGUAGE ~1, ~~1
diff --git a/llvm/test/tools/llvm-rc/Inputs/tokens.rc b/llvm/test/tools/llvm-rc/Inputs/tokens.rc
index 6a781202a7e37..20f77912477d9 100644
--- a/llvm/test/tools/llvm-rc/Inputs/tokens.rc
+++ b/llvm/test/tools/llvm-rc/Inputs/tokens.rc
@@ -1,4 +1,5 @@
1 + 2 - 3214L & 0x120894 032173 2|&~+(-7){0xabcdef 0xABCDEFl} Begin End
+1*3/4
He11o LLVM
identifier-with-dashes
diff --git a/llvm/test/tools/llvm-rc/parser-expr.test b/llvm/test/tools/llvm-rc/parser-expr.test
index ed6796529fdfa..14a299c9e3e96 100644
--- a/llvm/test/tools/llvm-rc/parser-expr.test
+++ b/llvm/test/tools/llvm-rc/parser-expr.test
@@ -7,6 +7,11 @@
; CHECK-NEXT: Language: 2, Sublanguage: 4
; CHECK-NEXT: Language: 3, Sublanguage: 5
; CHECK-NEXT: Language: 2, Sublanguage: 0
+; CHECK-NEXT: Language: 2, Sublanguage: 21
+; CHECK-NEXT: Language: 4, Sublanguage: 7
+; CHECK-NEXT: Language: 7, Sublanguage: 9
+; CHECK-NEXT: Language: 5, Sublanguage: 7
+; CHECK-NEXT: Language: 2, Sublanguage: 2
; CHECK-NEXT: Language: 4294967295, Sublanguage: 1
; CHECK-NEXT: Language: 1, Sublanguage: 4294967295
; CHECK-NEXT: Language: 4294967294, Sublanguage: 1
diff --git a/llvm/test/tools/llvm-rc/tokenizer.test b/llvm/test/tools/llvm-rc/tokenizer.test
index 8486f8bd78690..3062e2bf64629 100644
--- a/llvm/test/tools/llvm-rc/tokenizer.test
+++ b/llvm/test/tools/llvm-rc/tokenizer.test
@@ -25,6 +25,11 @@
; CHECK-NEXT: BlockEnd: }
; CHECK-NEXT: BlockBegin: Begin
; CHECK-NEXT: BlockEnd: End
+; CHECK-NEXT: Int: 1; int value = 1
+; CHECK-NEXT: Asterisk: *
+; CHECK-NEXT: Int: 3; int value = 3
+; CHECK-NEXT: Slash: /
+; CHECK-NEXT: Int: 4; int value = 4
; CHECK-NEXT: Identifier: He11o
; CHECK-NEXT: Identifier: LLVM
; CHECK-NEXT: Identifier: identifier-with-dashes
diff --git a/llvm/tools/llvm-rc/ResourceScriptParser.cpp b/llvm/tools/llvm-rc/ResourceScriptParser.cpp
index 69798152c1f25..e4efc83c933b4 100644
--- a/llvm/tools/llvm-rc/ResourceScriptParser.cpp
+++ b/llvm/tools/llvm-rc/ResourceScriptParser.cpp
@@ -132,12 +132,13 @@ void RCParser::consume() {
//
// The following grammar is used to parse the expressions Exp1:
// Exp1 ::= Exp2 || Exp1 + Exp2 || Exp1 - Exp2 || Exp1 | Exp2 || Exp1 & Exp2
-// Exp2 ::= -Exp2 || ~Exp2 || not Expr2 || Int || (Exp1).
-// (More conveniently, Exp1 is a non-empty sequence of Exp2 expressions,
-// separated by binary operators.)
+// Exp2 ::= Exp3 || Exp3 * Exp3 || Exp3 / Exp3
+// Exp3 ::= -Exp3 || ~Exp3 || not Expr3 || Int || (Exp1)
+// (More conveniently, Exp1 and Exp2 are non-empty sequences of Exp3
+// expressions, separated by binary operators.)
//
-// Expressions of type Exp1 are read by parseIntExpr1(Inner) method, while Exp2
-// is read by parseIntExpr2().
+// Expressions of type Exp1 are read by parseIntExpr1(Inner) method, Exp2
+// is read by parseIntExpr2() and Exp3 is read by parseIntExpr3().
//
// The original Microsoft tool handles multiple unary operators incorrectly.
// For example, in 16-bit little-endian integers:
@@ -158,7 +159,7 @@ Expected<IntWithNotMask> RCParser::parseIntExpr1() {
ASSIGN_OR_RETURN(FirstResult, parseIntExpr2());
IntWithNotMask Result = *FirstResult;
- while (!isEof() && look().isBinaryOp()) {
+ while (!isEof() && look().isLowPrecedenceBinaryOp()) {
auto OpToken = read();
ASSIGN_OR_RETURN(NextResult, parseIntExpr2());
@@ -180,7 +181,7 @@ Expected<IntWithNotMask> RCParser::parseIntExpr1() {
break;
default:
- llvm_unreachable("Already processed all binary ops.");
+ llvm_unreachable("Already processed all low precedence binary ops.");
}
}
@@ -188,7 +189,33 @@ Expected<IntWithNotMask> RCParser::parseIntExpr1() {
}
Expected<IntWithNotMask> RCParser::parseIntExpr2() {
- // Exp2 ::= -Exp2 || ~Exp2 || not Expr2 || Int || (Exp1).
+ // Exp2 ::= Exp3 || Exp3 * Exp3 || Exp3 / Exp3.
+ ASSIGN_OR_RETURN(FirstResult, parseIntExpr3());
+ IntWithNotMask Result = *FirstResult;
+
+ while (!isEof() && look().isHighPrecedenceBinaryOp()) {
+ auto OpToken = read();
+ ASSIGN_OR_RETURN(NextResult, parseIntExpr3());
+
+ switch (OpToken.kind()) {
+ case Kind::Asterisk:
+ Result *= *NextResult;
+ break;
+
+ case Kind::Slash:
+ Result /= *NextResult;
+ break;
+
+ default:
+ llvm_unreachable("Already processed all high precedence binary ops.");
+ }
+ }
+
+ return Result;
+}
+
+Expected<IntWithNotMask> RCParser::parseIntExpr3() {
+ // Exp3 ::= -Exp3 || ~Exp3 || not Expr3 || Int || (Exp1).
static const char ErrorMsg[] = "'-', '~', integer or '('";
if (isEof())
@@ -197,13 +224,13 @@ Expected<IntWithNotMask> RCParser::parseIntExpr2() {
switch (look().kind()) {
case Kind::Minus: {
consume();
- ASSIGN_OR_RETURN(Result, parseIntExpr2());
+ ASSIGN_OR_RETURN(Result, parseIntExpr3());
return -(*Result);
}
case Kind::Tilde: {
consume();
- ASSIGN_OR_RETURN(Result, parseIntExpr2());
+ ASSIGN_OR_RETURN(Result, parseIntExpr3());
return ~(*Result);
}
@@ -220,7 +247,7 @@ Expected<IntWithNotMask> RCParser::parseIntExpr2() {
case Kind::Identifier: {
if (!read().value().equals_insensitive("not"))
return getExpectedError(ErrorMsg, true);
- ASSIGN_OR_RETURN(Result, parseIntExpr2());
+ ASSIGN_OR_RETURN(Result, parseIntExpr3());
return IntWithNotMask(0, (*Result).getValue());
}
diff --git a/llvm/tools/llvm-rc/ResourceScriptParser.h b/llvm/tools/llvm-rc/ResourceScriptParser.h
index aa7f847187c49..1e7618c84142e 100644
--- a/llvm/tools/llvm-rc/ResourceScriptParser.h
+++ b/llvm/tools/llvm-rc/ResourceScriptParser.h
@@ -88,6 +88,7 @@ class RCParser {
// Helper integer expression parsing methods.
Expected<IntWithNotMask> parseIntExpr1();
Expected<IntWithNotMask> parseIntExpr2();
+ Expected<IntWithNotMask> parseIntExpr3();
// Advance the state by one, discarding the current token.
// If the discarded token had an incorrect type, fail.
diff --git a/llvm/tools/llvm-rc/ResourceScriptStmt.h b/llvm/tools/llvm-rc/ResourceScriptStmt.h
index 8f099202c0b47..a81e384fda365 100644
--- a/llvm/tools/llvm-rc/ResourceScriptStmt.h
+++ b/llvm/tools/llvm-rc/ResourceScriptStmt.h
@@ -49,6 +49,16 @@ class RCInt {
return *this;
}
+ RCInt &operator*=(const RCInt &Rhs) {
+ std::tie(Val, Long) = std::make_pair(Val * Rhs.Val, Long | Rhs.Long);
+ return *this;
+ }
+
+ RCInt &operator/=(const RCInt &Rhs) {
+ std::tie(Val, Long) = std::make_pair(Val / Rhs.Val, Long | Rhs.Long);
+ return *this;
+ }
+
RCInt &operator|=(const RCInt &Rhs) {
std::tie(Val, Long) = std::make_pair(Val | Rhs.Val, Long | Rhs.Long);
return *this;
@@ -98,6 +108,20 @@ class IntWithNotMask {
return *this;
}
+ IntWithNotMask &operator*=(const IntWithNotMask &Rhs) {
+ Value &= ~Rhs.NotMask;
+ Value *= Rhs.Value;
+ NotMask |= Rhs.NotMask;
+ return *this;
+ }
+
+ IntWithNotMask &operator/=(const IntWithNotMask &Rhs) {
+ Value &= ~Rhs.NotMask;
+ Value /= Rhs.Value;
+ NotMask |= Rhs.NotMask;
+ return *this;
+ }
+
IntWithNotMask &operator|=(const IntWithNotMask &Rhs) {
Value &= ~Rhs.NotMask;
Value |= Rhs.Value;
diff --git a/llvm/tools/llvm-rc/ResourceScriptToken.cpp b/llvm/tools/llvm-rc/ResourceScriptToken.cpp
index aad1060c4a381..0070037e63e6a 100644
--- a/llvm/tools/llvm-rc/ResourceScriptToken.cpp
+++ b/llvm/tools/llvm-rc/ResourceScriptToken.cpp
@@ -64,7 +64,7 @@ StringRef RCToken::value() const { return TokenValue; }
Kind RCToken::kind() const { return TokenKind; }
-bool RCToken::isBinaryOp() const {
+bool RCToken::isLowPrecedenceBinaryOp() const {
switch (TokenKind) {
case Kind::Plus:
case Kind::Minus:
@@ -76,6 +76,16 @@ bool RCToken::isBinaryOp() const {
}
}
+bool RCToken::isHighPrecedenceBinaryOp() const {
+ switch (TokenKind) {
+ case Kind::Asterisk:
+ case Kind::Slash:
+ return true;
+ default:
+ return false;
+ }
+}
+
static Error getStringError(const Twine &message) {
return make_error<StringError>("Error parsing file: " + message,
inconvertibleErrorCode());
diff --git a/llvm/tools/llvm-rc/ResourceScriptToken.h b/llvm/tools/llvm-rc/ResourceScriptToken.h
index 29f7502f89efd..3dcdfafd2d576 100644
--- a/llvm/tools/llvm-rc/ResourceScriptToken.h
+++ b/llvm/tools/llvm-rc/ResourceScriptToken.h
@@ -56,8 +56,11 @@ class RCToken {
StringRef value() const;
Kind kind() const;
- // Check if a token describes a binary operator.
- bool isBinaryOp() const;
+ // Check if a token describes a low precedence binary operator.
+ bool isLowPrecedenceBinaryOp() const;
+
+ // Check if a token describes a high precedence binary operator.
+ bool isHighPrecedenceBinaryOp() const;
private:
Kind TokenKind;
diff --git a/llvm/tools/llvm-rc/ResourceScriptTokenList.def b/llvm/tools/llvm-rc/ResourceScriptTokenList.def
index a61a96461f0fb..6ee13b2815d35 100644
--- a/llvm/tools/llvm-rc/ResourceScriptTokenList.def
+++ b/llvm/tools/llvm-rc/ResourceScriptTokenList.def
@@ -29,6 +29,8 @@ SHORT_TOKEN(BlockEnd, '}') // End of the block; can also be END.
SHORT_TOKEN(Comma, ',') // Comma - resource arguments separator.
SHORT_TOKEN(Plus, '+') // Addition operator.
SHORT_TOKEN(Minus, '-') // Subtraction operator.
+SHORT_TOKEN(Asterisk, '*') // Multiplication operator.
+SHORT_TOKEN(Slash, '/') // Division operator.
SHORT_TOKEN(Pipe, '|') // Bitwise-OR operator.
SHORT_TOKEN(Amp, '&') // Bitwise-AND operator.
SHORT_TOKEN(Tilde, '~') // Bitwise-NOT operator.
More information about the llvm-commits
mailing list