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