1//===-- ResourceScriptParser.cpp --------------------------------*- C++-*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===---------------------------------------------------------------------===//
8//
9// This implements the parser defined in ResourceScriptParser.h.
10//
11//===---------------------------------------------------------------------===//
12
13#include "ResourceScriptParser.h"
14#include "llvm/ADT/StringExtras.h"
15#include "llvm/Option/ArgList.h"
16#include "llvm/Support/FileSystem.h"
17#include "llvm/Support/Path.h"
18#include "llvm/Support/Process.h"
19
20// Take an expression returning llvm::Error and forward the error if it exists.
21#define RETURN_IF_ERROR(Expr) \
22 if (auto Err = (Expr)) \
23 return std::move(Err);
24
25// Take an expression returning llvm::Expected<T> and assign it to Var or
26// forward the error out of the function.
27#define ASSIGN_OR_RETURN(Var, Expr) \
28 auto Var = (Expr); \
29 if (!Var) \
30 return Var.takeError();
31
32namespace llvm {
33namespace rc {
34
35RCParser::ParserError::ParserError(const Twine &Expected, const LocIter CurLoc,
36 const LocIter End) {
37 CurMessage = "Error parsing file: expected " + Expected.str() + ", got " +
38 (CurLoc == End ? "<EOF>" : CurLoc->value()).str();
39}
40
41char RCParser::ParserError::ID = 0;
42
43RCParser::RCParser(std::vector<RCToken> TokenList)
44 : Tokens(std::move(TokenList)), CurLoc(Tokens.begin()), End(Tokens.end()) {}
45
46bool RCParser::isEof() const { return CurLoc == End; }
47
48RCParser::ParseType RCParser::parseSingleResource() {
49 // The first thing we read is usually a resource's name. However, in some
50 // cases (LANGUAGE and STRINGTABLE) the resources don't have their names
51 // and the first token to be read is the type.
52 ASSIGN_OR_RETURN(NameToken, readTypeOrName());
53
54 if (NameToken->equalsLower(Str: "LANGUAGE"))
55 return parseLanguageResource();
56 else if (NameToken->equalsLower(Str: "STRINGTABLE"))
57 return parseStringTableResource();
58
59 // If it's not an unnamed resource, what we've just read is a name. Now,
60 // read resource type;
61 ASSIGN_OR_RETURN(TypeToken, readTypeOrName());
62
63 ParseType Result = std::unique_ptr<RCResource>();
64 (void)!Result;
65
66 if (TypeToken->equalsLower(Str: "ACCELERATORS"))
67 Result = parseAcceleratorsResource();
68 else if (TypeToken->equalsLower(Str: "BITMAP"))
69 Result = parseBitmapResource();
70 else if (TypeToken->equalsLower(Str: "CURSOR"))
71 Result = parseCursorResource();
72 else if (TypeToken->equalsLower(Str: "DIALOG"))
73 Result = parseDialogResource(IsExtended: false);
74 else if (TypeToken->equalsLower(Str: "DIALOGEX"))
75 Result = parseDialogResource(IsExtended: true);
76 else if (TypeToken->equalsLower(Str: "HTML"))
77 Result = parseHTMLResource();
78 else if (TypeToken->equalsLower(Str: "ICON"))
79 Result = parseIconResource();
80 else if (TypeToken->equalsLower(Str: "MENU"))
81 Result = parseMenuResource();
82 else if (TypeToken->equalsLower(Str: "MENUEX"))
83 Result = parseMenuExResource();
84 else if (TypeToken->equalsLower(Str: "RCDATA"))
85 Result = parseUserDefinedResource(Type: RkRcData);
86 else if (TypeToken->equalsLower(Str: "VERSIONINFO"))
87 Result = parseVersionInfoResource();
88 else
89 Result = parseUserDefinedResource(Type: *TypeToken);
90
91 if (Result)
92 (*Result)->setName(*NameToken);
93
94 return Result;
95}
96
97bool RCParser::isNextTokenKind(Kind TokenKind) const {
98 return !isEof() && look().kind() == TokenKind;
99}
100
101const RCToken &RCParser::look() const {
102 assert(!isEof());
103 return *CurLoc;
104}
105
106const RCToken &RCParser::read() {
107 assert(!isEof());
108 return *CurLoc++;
109}
110
111void RCParser::consume() {
112 assert(!isEof());
113 CurLoc++;
114}
115
116// An integer description might consist of a single integer or
117// an arithmetic expression evaluating to the integer. The expressions
118// can contain the following tokens: <int> ( ) + - | & ~ not. Their meaning
119// is the same as in C++ except for 'not' expression.
120// The operators in the original RC implementation have the following
121// precedence:
122// 1) Unary operators (- ~ not),
123// 2) Binary operators (+ - & |), with no precedence.
124//
125// 'not' expression is mostly useful for style values. It evaluates to 0,
126// but value given to the operator is stored separately from integer value.
127// It's mostly useful for control style expressions and causes bits from
128// default control style to be excluded from generated style. For binary
129// operators the mask from the right operand is applied to the left operand
130// and masks from both operands are combined in operator result.
131//
132// The following grammar is used to parse the expressions Exp1:
133// Exp1 ::= Exp2 || Exp1 + Exp2 || Exp1 - Exp2 || Exp1 | Exp2 || Exp1 & Exp2
134// Exp2 ::= Exp3 || Exp3 * Exp3 || Exp3 / Exp3
135// Exp3 ::= -Exp3 || ~Exp3 || not Expr3 || Int || (Exp1)
136// (More conveniently, Exp1 and Exp2 are non-empty sequences of Exp3
137// expressions, separated by binary operators.)
138//
139// Expressions of type Exp1 are read by parseIntExpr1(Inner) method, Exp2
140// is read by parseIntExpr2() and Exp3 is read by parseIntExpr3().
141//
142// The original Microsoft tool handles multiple unary operators incorrectly.
143// For example, in 16-bit little-endian integers:
144// 1 => 01 00, -1 => ff ff, --1 => ff ff, ---1 => 01 00;
145// 1 => 01 00, ~1 => fe ff, ~~1 => fd ff, ~~~1 => fc ff.
146// Our implementation differs from the original one and handles these
147// operators correctly:
148// 1 => 01 00, -1 => ff ff, --1 => 01 00, ---1 => ff ff;
149// 1 => 01 00, ~1 => fe ff, ~~1 => 01 00, ~~~1 => fe ff.
150
151Expected<RCInt> RCParser::readInt() {
152 ASSIGN_OR_RETURN(Value, parseIntExpr1());
153 return (*Value).getValue();
154}
155
156Expected<IntWithNotMask> RCParser::parseIntExpr1() {
157 // Exp1 ::= Exp2 || Exp1 + Exp2 || Exp1 - Exp2 || Exp1 | Exp2 || Exp1 & Exp2.
158 ASSIGN_OR_RETURN(FirstResult, parseIntExpr2());
159 IntWithNotMask Result = *FirstResult;
160
161 while (!isEof() && look().isLowPrecedenceBinaryOp()) {
162 auto OpToken = read();
163 ASSIGN_OR_RETURN(NextResult, parseIntExpr2());
164
165 switch (OpToken.kind()) {
166 case Kind::Plus:
167 Result += *NextResult;
168 break;
169
170 case Kind::Minus:
171 Result -= *NextResult;
172 break;
173
174 case Kind::Pipe:
175 Result |= *NextResult;
176 break;
177
178 case Kind::Amp:
179 Result &= *NextResult;
180 break;
181
182 default:
183 llvm_unreachable("Already processed all low precedence binary ops.");
184 }
185 }
186
187 return Result;
188}
189
190Expected<IntWithNotMask> RCParser::parseIntExpr2() {
191 // Exp2 ::= Exp3 || Exp3 * Exp3 || Exp3 / Exp3.
192 ASSIGN_OR_RETURN(FirstResult, parseIntExpr3());
193 IntWithNotMask Result = *FirstResult;
194
195 while (!isEof() && look().isHighPrecedenceBinaryOp()) {
196 auto OpToken = read();
197 ASSIGN_OR_RETURN(NextResult, parseIntExpr3());
198
199 switch (OpToken.kind()) {
200 case Kind::Asterisk:
201 Result *= *NextResult;
202 break;
203
204 case Kind::Slash:
205 Result /= *NextResult;
206 break;
207
208 default:
209 llvm_unreachable("Already processed all high precedence binary ops.");
210 }
211 }
212
213 return Result;
214}
215
216Expected<IntWithNotMask> RCParser::parseIntExpr3() {
217 // Exp3 ::= -Exp3 || ~Exp3 || not Expr3 || Int || (Exp1).
218 static const char ErrorMsg[] = "'-', '~', integer or '('";
219
220 if (isEof())
221 return getExpectedError(Message: ErrorMsg);
222
223 switch (look().kind()) {
224 case Kind::Minus: {
225 consume();
226 ASSIGN_OR_RETURN(Result, parseIntExpr3());
227 return -(*Result);
228 }
229
230 case Kind::Tilde: {
231 consume();
232 ASSIGN_OR_RETURN(Result, parseIntExpr3());
233 return ~(*Result);
234 }
235
236 case Kind::Int:
237 return RCInt(read());
238
239 case Kind::LeftParen: {
240 consume();
241 ASSIGN_OR_RETURN(Result, parseIntExpr1());
242 RETURN_IF_ERROR(consumeType(Kind::RightParen));
243 return *Result;
244 }
245
246 case Kind::Identifier: {
247 if (!read().value().equals_insensitive(RHS: "not"))
248 return getExpectedError(Message: ErrorMsg, IsAlreadyRead: true);
249 ASSIGN_OR_RETURN(Result, parseIntExpr3());
250 return IntWithNotMask(0, (*Result).getValue());
251 }
252
253 default:
254 return getExpectedError(Message: ErrorMsg);
255 }
256}
257
258Expected<StringRef> RCParser::readString() {
259 if (!isNextTokenKind(TokenKind: Kind::String))
260 return getExpectedError(Message: "string");
261 return read().value();
262}
263
264Expected<StringRef> RCParser::readFilename() {
265 if (!isNextTokenKind(TokenKind: Kind::String) && !isNextTokenKind(TokenKind: Kind::Identifier))
266 return getExpectedError(Message: "string");
267 const RCToken &Token = read();
268 StringRef Str = Token.value();
269 if (Token.kind() != Kind::String)
270 return Str;
271 while (isNextTokenKind(TokenKind: Kind::String)) {
272 const RCToken &NextToken = read();
273 StringRef Next = NextToken.value();
274 bool IsWide = Str.consume_front_insensitive(Prefix: "L");
275 Next.consume_front_insensitive(Prefix: "L");
276 bool StrUnquoted = Str.consume_front(Prefix: "\"") && Str.consume_back(Suffix: "\"");
277 bool NextUnquoted = Next.consume_front(Prefix: "\"") && Next.consume_back(Suffix: "\"");
278 assert(StrUnquoted && NextUnquoted);
279 (void)StrUnquoted;
280 (void)NextUnquoted;
281
282 Str = Saver.save(S: Twine(IsWide ? "L" : "") + "\"" + Str + Next + "\"");
283 }
284 return Str;
285}
286
287Expected<StringRef> RCParser::readIdentifier() {
288 if (!isNextTokenKind(TokenKind: Kind::Identifier))
289 return getExpectedError(Message: "identifier");
290 return read().value();
291}
292
293Expected<IntOrString> RCParser::readIntOrString() {
294 if (!isNextTokenKind(TokenKind: Kind::Int) && !isNextTokenKind(TokenKind: Kind::String))
295 return getExpectedError(Message: "int or string");
296 return IntOrString(read());
297}
298
299Expected<IntOrString> RCParser::readTypeOrName() {
300 // We suggest that the correct resource name or type should be either an
301 // identifier or an integer. The original RC tool is much more liberal.
302 if (!isNextTokenKind(TokenKind: Kind::Identifier) && !isNextTokenKind(TokenKind: Kind::Int))
303 return getExpectedError(Message: "int or identifier");
304 return IntOrString(read());
305}
306
307Error RCParser::consumeType(Kind TokenKind) {
308 if (isNextTokenKind(TokenKind)) {
309 consume();
310 return Error::success();
311 }
312
313 switch (TokenKind) {
314#define TOKEN(TokenName) \
315 case Kind::TokenName: \
316 return getExpectedError(#TokenName);
317#define SHORT_TOKEN(TokenName, TokenCh) \
318 case Kind::TokenName: \
319 return getExpectedError(#TokenCh);
320#include "ResourceScriptTokenList.def"
321 }
322
323 llvm_unreachable("All case options exhausted.");
324}
325
326bool RCParser::consumeOptionalType(Kind TokenKind) {
327 if (isNextTokenKind(TokenKind)) {
328 consume();
329 return true;
330 }
331
332 return false;
333}
334
335Expected<SmallVector<RCInt, 8>> RCParser::readIntsWithCommas(size_t MinCount,
336 size_t MaxCount) {
337 assert(MinCount <= MaxCount);
338
339 SmallVector<RCInt, 8> Result;
340
341 auto FailureHandler =
342 [&](llvm::Error Err) -> Expected<SmallVector<RCInt, 8>> {
343 if (Result.size() < MinCount)
344 return std::move(Err);
345 consumeError(Err: std::move(Err));
346 return Result;
347 };
348
349 for (size_t i = 0; i < MaxCount; ++i) {
350 // Try to read a comma unless we read the first token.
351 // Sometimes RC tool requires them and sometimes not. We decide to
352 // always require them.
353 if (i >= 1) {
354 if (auto CommaError = consumeType(TokenKind: Kind::Comma))
355 return FailureHandler(std::move(CommaError));
356 }
357
358 if (auto IntResult = readInt())
359 Result.push_back(Elt: *IntResult);
360 else
361 return FailureHandler(IntResult.takeError());
362 }
363
364 return std::move(Result);
365}
366
367Expected<uint32_t> RCParser::parseFlags(ArrayRef<StringRef> FlagDesc,
368 ArrayRef<uint32_t> FlagValues) {
369 assert(!FlagDesc.empty());
370 assert(FlagDesc.size() == FlagValues.size());
371
372 uint32_t Result = 0;
373 while (isNextTokenKind(TokenKind: Kind::Comma)) {
374 consume();
375 ASSIGN_OR_RETURN(FlagResult, readIdentifier());
376 bool FoundFlag = false;
377
378 for (size_t FlagId = 0; FlagId < FlagDesc.size(); ++FlagId) {
379 if (!FlagResult->equals_insensitive(RHS: FlagDesc[FlagId]))
380 continue;
381
382 Result |= FlagValues[FlagId];
383 FoundFlag = true;
384 break;
385 }
386
387 if (!FoundFlag)
388 return getExpectedError(Message: join(R&: FlagDesc, Separator: "/"), IsAlreadyRead: true);
389 }
390
391 return Result;
392}
393
394uint16_t RCParser::parseMemoryFlags(uint16_t Flags) {
395 while (!isEof()) {
396 const RCToken &Token = look();
397 if (Token.kind() != Kind::Identifier)
398 return Flags;
399 const StringRef Ident = Token.value();
400 if (Ident.equals_insensitive(RHS: "PRELOAD"))
401 Flags |= MfPreload;
402 else if (Ident.equals_insensitive(RHS: "LOADONCALL"))
403 Flags &= ~MfPreload;
404 else if (Ident.equals_insensitive(RHS: "FIXED"))
405 Flags &= ~(MfMoveable | MfDiscardable);
406 else if (Ident.equals_insensitive(RHS: "MOVEABLE"))
407 Flags |= MfMoveable;
408 else if (Ident.equals_insensitive(RHS: "DISCARDABLE"))
409 Flags |= MfDiscardable | MfMoveable | MfPure;
410 else if (Ident.equals_insensitive(RHS: "PURE"))
411 Flags |= MfPure;
412 else if (Ident.equals_insensitive(RHS: "IMPURE"))
413 Flags &= ~(MfPure | MfDiscardable);
414 else if (Ident.equals_insensitive(RHS: "SHARED"))
415 Flags |= MfPure;
416 else if (Ident.equals_insensitive(RHS: "NONSHARED"))
417 Flags &= ~(MfPure | MfDiscardable);
418 else
419 return Flags;
420 consume();
421 }
422 return Flags;
423}
424
425Expected<OptionalStmtList>
426RCParser::parseOptionalStatements(OptStmtType StmtsType) {
427 OptionalStmtList Result;
428
429 // The last statement is always followed by the start of the block.
430 while (!isNextTokenKind(TokenKind: Kind::BlockBegin)) {
431 ASSIGN_OR_RETURN(SingleParse, parseSingleOptionalStatement(StmtsType));
432 Result.addStmt(Stmt: std::move(*SingleParse));
433 }
434
435 return std::move(Result);
436}
437
438Expected<std::unique_ptr<OptionalStmt>>
439RCParser::parseSingleOptionalStatement(OptStmtType StmtsType) {
440 ASSIGN_OR_RETURN(TypeToken, readIdentifier());
441 if (TypeToken->equals_insensitive(RHS: "CHARACTERISTICS"))
442 return parseCharacteristicsStmt();
443 if (TypeToken->equals_insensitive(RHS: "LANGUAGE"))
444 return parseLanguageStmt();
445 if (TypeToken->equals_insensitive(RHS: "VERSION"))
446 return parseVersionStmt();
447
448 if (StmtsType != OptStmtType::BasicStmt) {
449 if (TypeToken->equals_insensitive(RHS: "CAPTION"))
450 return parseCaptionStmt();
451 if (TypeToken->equals_insensitive(RHS: "CLASS"))
452 return parseClassStmt();
453 if (TypeToken->equals_insensitive(RHS: "EXSTYLE"))
454 return parseExStyleStmt();
455 if (TypeToken->equals_insensitive(RHS: "FONT"))
456 return parseFontStmt(DialogType: StmtsType);
457 if (TypeToken->equals_insensitive(RHS: "STYLE"))
458 return parseStyleStmt();
459 if (TypeToken->equals_insensitive(RHS: "MENU"))
460 return parseMenuStmt();
461 }
462
463 return getExpectedError(Message: "optional statement type, BEGIN or '{'",
464 /* IsAlreadyRead = */ true);
465}
466
467RCParser::ParseType RCParser::parseLanguageResource() {
468 // Read LANGUAGE as an optional statement. If it's read correctly, we can
469 // upcast it to RCResource.
470 return parseLanguageStmt();
471}
472
473RCParser::ParseType RCParser::parseAcceleratorsResource() {
474 uint16_t MemoryFlags =
475 parseMemoryFlags(Flags: AcceleratorsResource::getDefaultMemoryFlags());
476 ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements());
477 RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
478
479 auto Accels = std::make_unique<AcceleratorsResource>(
480 args: std::move(*OptStatements), args&: MemoryFlags);
481
482 while (!consumeOptionalType(TokenKind: Kind::BlockEnd)) {
483 ASSIGN_OR_RETURN(EventResult, readIntOrString());
484 RETURN_IF_ERROR(consumeType(Kind::Comma));
485 ASSIGN_OR_RETURN(IDResult, readInt());
486 ASSIGN_OR_RETURN(
487 FlagsResult,
488 parseFlags(AcceleratorsResource::Accelerator::OptionsStr,
489 AcceleratorsResource::Accelerator::OptionsFlags));
490 Accels->addAccelerator(Event: *EventResult, Id: *IDResult, Flags: *FlagsResult);
491 }
492
493 return std::move(Accels);
494}
495
496RCParser::ParseType RCParser::parseCursorResource() {
497 uint16_t MemoryFlags =
498 parseMemoryFlags(Flags: CursorResource::getDefaultMemoryFlags());
499 ASSIGN_OR_RETURN(Arg, readFilename());
500 return std::make_unique<CursorResource>(args&: *Arg, args&: MemoryFlags);
501}
502
503RCParser::ParseType RCParser::parseDialogResource(bool IsExtended) {
504 uint16_t MemoryFlags =
505 parseMemoryFlags(Flags: DialogResource::getDefaultMemoryFlags());
506 // Dialog resources have the following format of the arguments:
507 // DIALOG: x, y, width, height [opt stmts...] {controls...}
508 // DIALOGEX: x, y, width, height [, helpID] [opt stmts...] {controls...}
509 // These are very similar, so we parse them together.
510 ASSIGN_OR_RETURN(LocResult, readIntsWithCommas(4, 4));
511
512 uint32_t HelpID = 0; // When HelpID is unset, it's assumed to be 0.
513 if (IsExtended && consumeOptionalType(TokenKind: Kind::Comma)) {
514 ASSIGN_OR_RETURN(HelpIDResult, readInt());
515 HelpID = *HelpIDResult;
516 }
517
518 ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements(
519 IsExtended ? OptStmtType::DialogExStmt
520 : OptStmtType::DialogStmt));
521
522 assert(isNextTokenKind(Kind::BlockBegin) &&
523 "parseOptionalStatements, when successful, halts on BlockBegin.");
524 consume();
525
526 auto Dialog = std::make_unique<DialogResource>(
527 args&: (*LocResult)[0], args&: (*LocResult)[1], args&: (*LocResult)[2], args&: (*LocResult)[3],
528 args&: HelpID, args: std::move(*OptStatements), args&: IsExtended, args&: MemoryFlags);
529
530 while (!consumeOptionalType(TokenKind: Kind::BlockEnd)) {
531 ASSIGN_OR_RETURN(ControlDefResult, parseControl());
532 Dialog->addControl(Ctl: std::move(*ControlDefResult));
533 }
534
535 return std::move(Dialog);
536}
537
538RCParser::ParseType RCParser::parseUserDefinedResource(IntOrString Type) {
539 uint16_t MemoryFlags =
540 parseMemoryFlags(Flags: UserDefinedResource::getDefaultMemoryFlags());
541 if (isEof())
542 return getExpectedError(Message: "filename, '{' or BEGIN");
543
544 // Check if this is a file resource.
545 switch (look().kind()) {
546 case Kind::String:
547 case Kind::Identifier: {
548 ASSIGN_OR_RETURN(Filename, readFilename());
549 return std::make_unique<UserDefinedResource>(args&: Type, args&: *Filename, args&: MemoryFlags);
550 }
551 default:
552 break;
553 }
554
555 RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
556 std::vector<IntOrString> Data;
557
558 while (!consumeOptionalType(TokenKind: Kind::BlockEnd)) {
559 ASSIGN_OR_RETURN(Item, readIntOrString());
560 Data.push_back(x: *Item);
561
562 // There can be zero or more commas after each token (but not before
563 // the first one).
564 while (consumeOptionalType(TokenKind: Kind::Comma)) {
565 }
566 }
567
568 return std::make_unique<UserDefinedResource>(args&: Type, args: std::move(Data),
569 args&: MemoryFlags);
570}
571
572RCParser::ParseType RCParser::parseVersionInfoResource() {
573 uint16_t MemoryFlags =
574 parseMemoryFlags(Flags: VersionInfoResource::getDefaultMemoryFlags());
575 ASSIGN_OR_RETURN(FixedResult, parseVersionInfoFixed());
576 ASSIGN_OR_RETURN(BlockResult, parseVersionInfoBlockContents(StringRef()));
577 return std::make_unique<VersionInfoResource>(
578 args: std::move(**BlockResult), args: std::move(*FixedResult), args&: MemoryFlags);
579}
580
581Expected<Control> RCParser::parseControl() {
582 // Each control definition (except CONTROL) follows one of the schemes below
583 // depending on the control class:
584 // [class] text, id, x, y, width, height [, style] [, exstyle] [, helpID]
585 // [class] id, x, y, width, height [, style] [, exstyle] [, helpID]
586 // Note that control ids must be integers.
587 // Text might be either a string or an integer pointing to resource ID.
588 ASSIGN_OR_RETURN(ClassResult, readIdentifier());
589 std::string ClassUpper = ClassResult->upper();
590 auto CtlInfo = Control::SupportedCtls.find(Key: ClassUpper);
591 if (CtlInfo == Control::SupportedCtls.end())
592 return getExpectedError(Message: "control type, END or '}'", IsAlreadyRead: true);
593
594 // Read caption if necessary.
595 IntOrString Caption{StringRef()};
596 if (CtlInfo->getValue().HasTitle) {
597 ASSIGN_OR_RETURN(CaptionResult, readIntOrString());
598 RETURN_IF_ERROR(consumeType(Kind::Comma));
599 Caption = *CaptionResult;
600 }
601
602 ASSIGN_OR_RETURN(ID, readInt());
603 RETURN_IF_ERROR(consumeType(Kind::Comma));
604
605 IntOrString Class;
606 std::optional<IntWithNotMask> Style;
607 if (ClassUpper == "CONTROL") {
608 // CONTROL text, id, class, style, x, y, width, height [, exstyle] [,
609 // helpID]
610 ASSIGN_OR_RETURN(ClassStr, readString());
611 RETURN_IF_ERROR(consumeType(Kind::Comma));
612 Class = *ClassStr;
613 ASSIGN_OR_RETURN(StyleVal, parseIntExpr1());
614 RETURN_IF_ERROR(consumeType(Kind::Comma));
615 Style = *StyleVal;
616 } else {
617 Class = CtlInfo->getValue().CtlClass;
618 }
619
620 // x, y, width, height
621 ASSIGN_OR_RETURN(Args, readIntsWithCommas(4, 4));
622
623 if (ClassUpper != "CONTROL") {
624 if (consumeOptionalType(TokenKind: Kind::Comma)) {
625 ASSIGN_OR_RETURN(Val, parseIntExpr1());
626 Style = *Val;
627 }
628 }
629
630 std::optional<uint32_t> ExStyle;
631 if (consumeOptionalType(TokenKind: Kind::Comma)) {
632 ASSIGN_OR_RETURN(Val, readInt());
633 ExStyle = *Val;
634 }
635 std::optional<uint32_t> HelpID;
636 if (consumeOptionalType(TokenKind: Kind::Comma)) {
637 ASSIGN_OR_RETURN(Val, readInt());
638 HelpID = *Val;
639 }
640
641 return Control(*ClassResult, Caption, *ID, (*Args)[0], (*Args)[1], (*Args)[2],
642 (*Args)[3], Style, ExStyle, HelpID, Class);
643}
644
645RCParser::ParseType RCParser::parseBitmapResource() {
646 uint16_t MemoryFlags =
647 parseMemoryFlags(Flags: BitmapResource::getDefaultMemoryFlags());
648 ASSIGN_OR_RETURN(Arg, readFilename());
649 return std::make_unique<BitmapResource>(args&: *Arg, args&: MemoryFlags);
650}
651
652RCParser::ParseType RCParser::parseIconResource() {
653 uint16_t MemoryFlags =
654 parseMemoryFlags(Flags: IconResource::getDefaultMemoryFlags());
655 ASSIGN_OR_RETURN(Arg, readFilename());
656 return std::make_unique<IconResource>(args&: *Arg, args&: MemoryFlags);
657}
658
659RCParser::ParseType RCParser::parseHTMLResource() {
660 uint16_t MemoryFlags =
661 parseMemoryFlags(Flags: HTMLResource::getDefaultMemoryFlags());
662 ASSIGN_OR_RETURN(Arg, readFilename());
663 return std::make_unique<HTMLResource>(args&: *Arg, args&: MemoryFlags);
664}
665
666RCParser::ParseType RCParser::parseMenuResource() {
667 uint16_t MemoryFlags =
668 parseMemoryFlags(Flags: MenuResource::getDefaultMemoryFlags());
669 ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements());
670 ASSIGN_OR_RETURN(Items, parseMenuItemsList());
671 return std::make_unique<MenuResource>(args: std::move(*OptStatements),
672 args: std::move(*Items), args&: MemoryFlags);
673}
674
675RCParser::ParseType RCParser::parseMenuExResource() {
676 uint16_t MemoryFlags =
677 parseMemoryFlags(Flags: MenuExResource::getDefaultMemoryFlags());
678 ASSIGN_OR_RETURN(Items, parseMenuExItemsList());
679 return std::make_unique<MenuExResource>(args: std::move(*Items), args&: MemoryFlags);
680}
681
682Expected<MenuDefinitionList> RCParser::parseMenuItemsList() {
683 RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
684
685 MenuDefinitionList List;
686
687 // Read a set of items. Each item is of one of three kinds:
688 // MENUITEM SEPARATOR
689 // MENUITEM caption:String, result:Int [, menu flags]...
690 // POPUP caption:String [, menu flags]... { items... }
691 while (!consumeOptionalType(TokenKind: Kind::BlockEnd)) {
692 ASSIGN_OR_RETURN(ItemTypeResult, readIdentifier());
693
694 bool IsMenuItem = ItemTypeResult->equals_insensitive(RHS: "MENUITEM");
695 bool IsPopup = ItemTypeResult->equals_insensitive(RHS: "POPUP");
696 if (!IsMenuItem && !IsPopup)
697 return getExpectedError(Message: "MENUITEM, POPUP, END or '}'", IsAlreadyRead: true);
698
699 if (IsMenuItem && isNextTokenKind(TokenKind: Kind::Identifier)) {
700 // Now, expecting SEPARATOR.
701 ASSIGN_OR_RETURN(SeparatorResult, readIdentifier());
702 if (SeparatorResult->equals_insensitive(RHS: "SEPARATOR")) {
703 List.addDefinition(Def: std::make_unique<MenuSeparator>());
704 continue;
705 }
706
707 return getExpectedError(Message: "SEPARATOR or string", IsAlreadyRead: true);
708 }
709
710 // Not a separator. Read the caption.
711 ASSIGN_OR_RETURN(CaptionResult, readString());
712
713 // If MENUITEM, expect also a comma and an integer.
714 uint32_t MenuResult = -1;
715
716 if (IsMenuItem) {
717 RETURN_IF_ERROR(consumeType(Kind::Comma));
718 ASSIGN_OR_RETURN(IntResult, readInt());
719 MenuResult = *IntResult;
720 }
721
722 ASSIGN_OR_RETURN(FlagsResult, parseFlags(MenuDefinition::OptionsStr,
723 MenuDefinition::OptionsFlags));
724
725 if (IsPopup) {
726 // If POPUP, read submenu items recursively.
727 ASSIGN_OR_RETURN(SubMenuResult, parseMenuItemsList());
728 List.addDefinition(Def: std::make_unique<PopupItem>(
729 args&: *CaptionResult, args&: *FlagsResult, args: std::move(*SubMenuResult)));
730 continue;
731 }
732
733 assert(IsMenuItem);
734 List.addDefinition(
735 Def: std::make_unique<MenuItem>(args&: *CaptionResult, args&: MenuResult, args&: *FlagsResult));
736 }
737
738 return std::move(List);
739}
740
741Expected<MenuDefinitionList> RCParser::parseMenuExItemsList() {
742 RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
743
744 MenuDefinitionList List;
745
746 // Read a set of items. Each item is of one of two kinds:
747 // MENUITEM caption:String [,[id][, [type][, state]]]]
748 // POPUP caption:String [,[id][, [type][, [state][, helpID]]]] { popupBody }
749 while (!consumeOptionalType(TokenKind: Kind::BlockEnd)) {
750 ASSIGN_OR_RETURN(ItemTypeResult, readIdentifier());
751
752 bool IsMenuItem = ItemTypeResult->equals_insensitive(RHS: "MENUITEM");
753 bool IsPopup = ItemTypeResult->equals_insensitive(RHS: "POPUP");
754 if (!IsMenuItem && !IsPopup)
755 return getExpectedError(Message: "MENUITEM, POPUP, END or '}'", IsAlreadyRead: true);
756
757 // Not a separator. Read the caption.
758 ASSIGN_OR_RETURN(CaptionResult, readString());
759
760 // If MENUITEM, expect [,[id][, [type][, state]]]]
761 if (IsMenuItem) {
762 uint32_t MenuId = 0;
763 uint32_t MenuType = 0;
764 uint32_t MenuState = 0;
765
766 if (consumeOptionalType(TokenKind: Kind::Comma)) {
767 auto IntId = readInt();
768 if (IntId) {
769 MenuId = *IntId;
770 }
771 if (consumeOptionalType(TokenKind: Kind::Comma)) {
772 auto IntType = readInt();
773 if (IntType) {
774 MenuType = *IntType;
775 }
776 if (consumeOptionalType(TokenKind: Kind::Comma)) {
777 auto IntState = readInt();
778 if (IntState) {
779 MenuState = *IntState;
780 }
781 }
782 }
783 }
784 List.addDefinition(Def: std::make_unique<MenuExItem>(args&: *CaptionResult, args&: MenuId,
785 args&: MenuType, args&: MenuState));
786 continue;
787 }
788
789 assert(IsPopup);
790
791 uint32_t PopupId = 0;
792 uint32_t PopupType = 0;
793 uint32_t PopupState = 0;
794 uint32_t PopupHelpID = 0;
795
796 if (consumeOptionalType(TokenKind: Kind::Comma)) {
797 auto IntId = readInt();
798 if (IntId) {
799 PopupId = *IntId;
800 }
801 if (consumeOptionalType(TokenKind: Kind::Comma)) {
802 auto IntType = readInt();
803 if (IntType) {
804 PopupType = *IntType;
805 }
806 if (consumeOptionalType(TokenKind: Kind::Comma)) {
807 auto IntState = readInt();
808 if (IntState) {
809 PopupState = *IntState;
810 }
811 if (consumeOptionalType(TokenKind: Kind::Comma)) {
812 auto IntHelpID = readInt();
813 if (IntHelpID) {
814 PopupHelpID = *IntHelpID;
815 }
816 }
817 }
818 }
819 }
820 // If POPUP, read submenu items recursively.
821 ASSIGN_OR_RETURN(SubMenuResult, parseMenuExItemsList());
822 List.addDefinition(Def: std::make_unique<PopupExItem>(
823 args&: *CaptionResult, args&: PopupId, args&: PopupType, args&: PopupState, args&: PopupHelpID,
824 args: std::move(*SubMenuResult)));
825 }
826
827 return std::move(List);
828}
829
830RCParser::ParseType RCParser::parseStringTableResource() {
831 uint16_t MemoryFlags =
832 parseMemoryFlags(Flags: StringTableResource::getDefaultMemoryFlags());
833 ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements());
834 RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
835
836 auto Table = std::make_unique<StringTableResource>(args: std::move(*OptStatements),
837 args&: MemoryFlags);
838
839 // Read strings until we reach the end of the block.
840 while (!consumeOptionalType(TokenKind: Kind::BlockEnd)) {
841 // Each definition consists of string's ID (an integer) and a string.
842 // Some examples in documentation suggest that there might be a comma in
843 // between, however we strictly adhere to the single statement definition.
844 ASSIGN_OR_RETURN(IDResult, readInt());
845 consumeOptionalType(TokenKind: Kind::Comma);
846
847 std::vector<StringRef> Strings;
848 ASSIGN_OR_RETURN(StrResult, readString());
849 Strings.push_back(x: *StrResult);
850 while (isNextTokenKind(TokenKind: Kind::String))
851 Strings.push_back(x: read().value());
852
853 Table->addStrings(ID: *IDResult, Strings: std::move(Strings));
854 }
855
856 return std::move(Table);
857}
858
859Expected<std::unique_ptr<VersionInfoBlock>>
860RCParser::parseVersionInfoBlockContents(StringRef BlockName) {
861 RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
862
863 auto Contents = std::make_unique<VersionInfoBlock>(args&: BlockName);
864
865 while (!isNextTokenKind(TokenKind: Kind::BlockEnd)) {
866 ASSIGN_OR_RETURN(Stmt, parseVersionInfoStmt());
867 Contents->addStmt(Stmt: std::move(*Stmt));
868 }
869
870 consume(); // Consume BlockEnd.
871
872 return std::move(Contents);
873}
874
875Expected<std::unique_ptr<VersionInfoStmt>> RCParser::parseVersionInfoStmt() {
876 // Expect either BLOCK or VALUE, then a name or a key (a string).
877 ASSIGN_OR_RETURN(TypeResult, readIdentifier());
878
879 if (TypeResult->equals_insensitive(RHS: "BLOCK")) {
880 ASSIGN_OR_RETURN(NameResult, readString());
881 return parseVersionInfoBlockContents(BlockName: *NameResult);
882 }
883
884 if (TypeResult->equals_insensitive(RHS: "VALUE")) {
885 ASSIGN_OR_RETURN(KeyResult, readString());
886 // Read a non-empty list of strings and/or ints, each
887 // possibly preceded by a comma. Unfortunately, the tool behavior depends
888 // on them existing or not, so we need to memorize where we found them.
889 std::vector<IntOrString> Values;
890 BitVector PrecedingCommas;
891 RETURN_IF_ERROR(consumeType(Kind::Comma));
892 while (!isNextTokenKind(TokenKind: Kind::Identifier) &&
893 !isNextTokenKind(TokenKind: Kind::BlockEnd)) {
894 // Try to eat a comma if it's not the first statement.
895 bool HadComma = Values.size() > 0 && consumeOptionalType(TokenKind: Kind::Comma);
896 ASSIGN_OR_RETURN(ValueResult, readIntOrString());
897 Values.push_back(x: *ValueResult);
898 PrecedingCommas.push_back(Val: HadComma);
899 }
900 return std::make_unique<VersionInfoValue>(args&: *KeyResult, args: std::move(Values),
901 args: std::move(PrecedingCommas));
902 }
903
904 return getExpectedError(Message: "BLOCK or VALUE", IsAlreadyRead: true);
905}
906
907Expected<VersionInfoResource::VersionInfoFixed>
908RCParser::parseVersionInfoFixed() {
909 using RetType = VersionInfoResource::VersionInfoFixed;
910 RetType Result;
911
912 // Read until the beginning of the block.
913 while (!isNextTokenKind(TokenKind: Kind::BlockBegin)) {
914 ASSIGN_OR_RETURN(TypeResult, readIdentifier());
915 auto FixedType = RetType::getFixedType(Type: *TypeResult);
916
917 if (!RetType::isTypeSupported(Type: FixedType))
918 return getExpectedError(Message: "fixed VERSIONINFO statement type", IsAlreadyRead: true);
919 if (Result.IsTypePresent[FixedType])
920 return getExpectedError(Message: "yet unread fixed VERSIONINFO statement type",
921 IsAlreadyRead: true);
922
923 // VERSION variations take multiple integers.
924 size_t NumInts = RetType::isVersionType(Type: FixedType) ? 4 : 1;
925 ASSIGN_OR_RETURN(ArgsResult, readIntsWithCommas(1, NumInts));
926 SmallVector<uint32_t, 4> ArgInts(ArgsResult->begin(), ArgsResult->end());
927 while (ArgInts.size() < NumInts)
928 ArgInts.push_back(Elt: 0);
929 Result.setValue(Type: FixedType, Value: ArgInts);
930 }
931
932 return Result;
933}
934
935RCParser::ParseOptionType RCParser::parseLanguageStmt() {
936 ASSIGN_OR_RETURN(Args, readIntsWithCommas(/* min = */ 2, /* max = */ 2));
937 return std::make_unique<LanguageResource>(args&: (*Args)[0], args&: (*Args)[1]);
938}
939
940RCParser::ParseOptionType RCParser::parseCharacteristicsStmt() {
941 ASSIGN_OR_RETURN(Arg, readInt());
942 return std::make_unique<CharacteristicsStmt>(args&: *Arg);
943}
944
945RCParser::ParseOptionType RCParser::parseVersionStmt() {
946 ASSIGN_OR_RETURN(Arg, readInt());
947 return std::make_unique<VersionStmt>(args&: *Arg);
948}
949
950RCParser::ParseOptionType RCParser::parseCaptionStmt() {
951 ASSIGN_OR_RETURN(Arg, readString());
952 return std::make_unique<CaptionStmt>(args&: *Arg);
953}
954
955RCParser::ParseOptionType RCParser::parseClassStmt() {
956 ASSIGN_OR_RETURN(Arg, readIntOrString());
957 return std::make_unique<ClassStmt>(args&: *Arg);
958}
959
960RCParser::ParseOptionType RCParser::parseFontStmt(OptStmtType DialogType) {
961 assert(DialogType != OptStmtType::BasicStmt);
962
963 ASSIGN_OR_RETURN(SizeResult, readInt());
964 RETURN_IF_ERROR(consumeType(Kind::Comma));
965 ASSIGN_OR_RETURN(NameResult, readString());
966
967 // Default values for the optional arguments.
968 uint32_t FontWeight = 0;
969 bool FontItalic = false;
970 uint32_t FontCharset = 1;
971 if (DialogType == OptStmtType::DialogExStmt) {
972 if (consumeOptionalType(TokenKind: Kind::Comma)) {
973 ASSIGN_OR_RETURN(Args, readIntsWithCommas(/* min = */ 0, /* max = */ 3));
974 if (Args->size() >= 1)
975 FontWeight = (*Args)[0];
976 if (Args->size() >= 2)
977 FontItalic = (*Args)[1] != 0;
978 if (Args->size() >= 3)
979 FontCharset = (*Args)[2];
980 }
981 }
982 return std::make_unique<FontStmt>(args&: *SizeResult, args&: *NameResult, args&: FontWeight,
983 args&: FontItalic, args&: FontCharset);
984}
985
986RCParser::ParseOptionType RCParser::parseStyleStmt() {
987 ASSIGN_OR_RETURN(Arg, readInt());
988 return std::make_unique<StyleStmt>(args&: *Arg);
989}
990
991RCParser::ParseOptionType RCParser::parseExStyleStmt() {
992 ASSIGN_OR_RETURN(Arg, readInt());
993 return std::make_unique<ExStyleStmt>(args&: *Arg);
994}
995
996RCParser::ParseOptionType RCParser::parseMenuStmt() {
997 ASSIGN_OR_RETURN(Arg, readIntOrString());
998 return std::make_unique<MenuStmt>(args&: *Arg);
999}
1000
1001Error RCParser::getExpectedError(const Twine &Message, bool IsAlreadyRead) {
1002 return make_error<ParserError>(
1003 Args: Message, Args: IsAlreadyRead ? std::prev(x: CurLoc) : CurLoc, Args: End);
1004}
1005
1006} // namespace rc
1007} // namespace llvm
1008