xref: /llvm-project/llvm/tools/llvm-rc/ResourceScriptParser.cpp (revision 4ac54d930280b0d4c774ea51fb52beb81af78190)
1 //===-- ResourceScriptParser.cpp --------------------------------*- C++-*-===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===---------------------------------------------------------------------===//
9 //
10 // This implements the parser defined in ResourceScriptParser.h.
11 //
12 //===---------------------------------------------------------------------===//
13 
14 #include "ResourceScriptParser.h"
15 
16 // Take an expression returning llvm::Error and forward the error if it exists.
17 #define RETURN_IF_ERROR(Expr)                                                  \
18   if (auto Err = (Expr))                                                       \
19     return std::move(Err);
20 
21 // Take an expression returning llvm::Expected<T> and assign it to Var or
22 // forward the error out of the function.
23 #define ASSIGN_OR_RETURN(Var, Expr)                                            \
24   auto Var = (Expr);                                                           \
25   if (!Var)                                                                    \
26     return Var.takeError();
27 
28 namespace llvm {
29 namespace rc {
30 
31 RCParser::ParserError::ParserError(const Twine Expected, const LocIter CurLoc,
32                                    const LocIter End)
33     : ErrorLoc(CurLoc), FileEnd(End) {
34   CurMessage = "Error parsing file: expected " + Expected.str() + ", got " +
35                (CurLoc == End ? "<EOF>" : CurLoc->value()).str();
36 }
37 
38 char RCParser::ParserError::ID = 0;
39 
40 RCParser::RCParser(const std::vector<RCToken> &TokenList)
41     : Tokens(TokenList), CurLoc(Tokens.begin()), End(Tokens.end()) {}
42 
43 RCParser::RCParser(std::vector<RCToken> &&TokenList)
44     : Tokens(std::move(TokenList)), CurLoc(Tokens.begin()), End(Tokens.end()) {}
45 
46 bool RCParser::isEof() const { return CurLoc == End; }
47 
48 RCParser::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("LANGUAGE"))
55     return parseLanguageResource();
56   else if (NameToken->equalsLower("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("ACCELERATORS"))
67     Result = parseAcceleratorsResource();
68   else if (TypeToken->equalsLower("CURSOR"))
69     Result = parseCursorResource();
70   else if (TypeToken->equalsLower("DIALOG"))
71     Result = parseDialogResource(false);
72   else if (TypeToken->equalsLower("DIALOGEX"))
73     Result = parseDialogResource(true);
74   else if (TypeToken->equalsLower("ICON"))
75     Result = parseIconResource();
76   else if (TypeToken->equalsLower("HTML"))
77     Result = parseHTMLResource();
78   else if (TypeToken->equalsLower("MENU"))
79     Result = parseMenuResource();
80   else
81     return getExpectedError("resource type", /* IsAlreadyRead = */ true);
82 
83   if (Result)
84     (*Result)->setName(*NameToken);
85 
86   return Result;
87 }
88 
89 bool RCParser::isNextTokenKind(Kind TokenKind) const {
90   return !isEof() && look().kind() == TokenKind;
91 }
92 
93 const RCToken &RCParser::look() const {
94   assert(!isEof());
95   return *CurLoc;
96 }
97 
98 const RCToken &RCParser::read() {
99   assert(!isEof());
100   return *CurLoc++;
101 }
102 
103 void RCParser::consume() {
104   assert(!isEof());
105   CurLoc++;
106 }
107 
108 Expected<uint32_t> RCParser::readInt() {
109   if (!isNextTokenKind(Kind::Int))
110     return getExpectedError("integer");
111   return read().intValue();
112 }
113 
114 Expected<StringRef> RCParser::readString() {
115   if (!isNextTokenKind(Kind::String))
116     return getExpectedError("string");
117   return read().value();
118 }
119 
120 Expected<StringRef> RCParser::readIdentifier() {
121   if (!isNextTokenKind(Kind::Identifier))
122     return getExpectedError("identifier");
123   return read().value();
124 }
125 
126 Expected<IntOrString> RCParser::readIntOrString() {
127   if (!isNextTokenKind(Kind::Int) && !isNextTokenKind(Kind::String))
128     return getExpectedError("int or string");
129   return IntOrString(read());
130 }
131 
132 Expected<IntOrString> RCParser::readTypeOrName() {
133   // We suggest that the correct resource name or type should be either an
134   // identifier or an integer. The original RC tool is much more liberal.
135   if (!isNextTokenKind(Kind::Identifier) && !isNextTokenKind(Kind::Int))
136     return getExpectedError("int or identifier");
137   return IntOrString(read());
138 }
139 
140 Error RCParser::consumeType(Kind TokenKind) {
141   if (isNextTokenKind(TokenKind)) {
142     consume();
143     return Error::success();
144   }
145 
146   switch (TokenKind) {
147 #define TOKEN(TokenName)                                                       \
148   case Kind::TokenName:                                                        \
149     return getExpectedError(#TokenName);
150 #define SHORT_TOKEN(TokenName, TokenCh)                                        \
151   case Kind::TokenName:                                                        \
152     return getExpectedError(#TokenCh);
153 #include "ResourceScriptTokenList.h"
154 #undef SHORT_TOKEN
155 #undef TOKEN
156   }
157 
158   llvm_unreachable("All case options exhausted.");
159 }
160 
161 bool RCParser::consumeOptionalType(Kind TokenKind) {
162   if (isNextTokenKind(TokenKind)) {
163     consume();
164     return true;
165   }
166 
167   return false;
168 }
169 
170 Expected<SmallVector<uint32_t, 8>>
171 RCParser::readIntsWithCommas(size_t MinCount, size_t MaxCount) {
172   assert(MinCount <= MaxCount);
173 
174   SmallVector<uint32_t, 8> Result;
175 
176   auto FailureHandler =
177       [&](llvm::Error Err) -> Expected<SmallVector<uint32_t, 8>> {
178     if (Result.size() < MinCount)
179       return std::move(Err);
180     consumeError(std::move(Err));
181     return Result;
182   };
183 
184   for (size_t i = 0; i < MaxCount; ++i) {
185     // Try to read a comma unless we read the first token.
186     // Sometimes RC tool requires them and sometimes not. We decide to
187     // always require them.
188     if (i >= 1) {
189       if (auto CommaError = consumeType(Kind::Comma))
190         return FailureHandler(std::move(CommaError));
191     }
192 
193     if (auto IntResult = readInt())
194       Result.push_back(*IntResult);
195     else
196       return FailureHandler(IntResult.takeError());
197   }
198 
199   return std::move(Result);
200 }
201 
202 Expected<uint32_t> RCParser::parseFlags(ArrayRef<StringRef> FlagDesc) {
203   assert(FlagDesc.size() <= 32 && "More than 32 flags won't fit in result.");
204   assert(!FlagDesc.empty());
205 
206   uint32_t Result = 0;
207   while (isNextTokenKind(Kind::Comma)) {
208     consume();
209     ASSIGN_OR_RETURN(FlagResult, readIdentifier());
210     bool FoundFlag = false;
211 
212     for (size_t FlagId = 0; FlagId < FlagDesc.size(); ++FlagId) {
213       if (!FlagResult->equals_lower(FlagDesc[FlagId]))
214         continue;
215 
216       Result |= (1U << FlagId);
217       FoundFlag = true;
218       break;
219     }
220 
221     if (!FoundFlag)
222       return getExpectedError(join(FlagDesc, "/"), true);
223   }
224 
225   return Result;
226 }
227 
228 // As for now, we ignore the extended set of statements.
229 Expected<OptionalStmtList> RCParser::parseOptionalStatements(bool IsExtended) {
230   OptionalStmtList Result;
231 
232   // The last statement is always followed by the start of the block.
233   while (!isNextTokenKind(Kind::BlockBegin)) {
234     ASSIGN_OR_RETURN(SingleParse, parseSingleOptionalStatement(IsExtended));
235     Result.addStmt(std::move(*SingleParse));
236   }
237 
238   return std::move(Result);
239 }
240 
241 Expected<std::unique_ptr<OptionalStmt>>
242 RCParser::parseSingleOptionalStatement(bool IsExtended) {
243   ASSIGN_OR_RETURN(TypeToken, readIdentifier());
244   if (TypeToken->equals_lower("CHARACTERISTICS"))
245     return parseCharacteristicsStmt();
246   if (TypeToken->equals_lower("LANGUAGE"))
247     return parseLanguageStmt();
248   if (TypeToken->equals_lower("VERSION"))
249     return parseVersionStmt();
250 
251   if (IsExtended) {
252     if (TypeToken->equals_lower("CAPTION"))
253       return parseCaptionStmt();
254     if (TypeToken->equals_lower("FONT"))
255       return parseFontStmt();
256     if (TypeToken->equals_lower("STYLE"))
257       return parseStyleStmt();
258   }
259 
260   return getExpectedError("optional statement type, BEGIN or '{'",
261                           /* IsAlreadyRead = */ true);
262 }
263 
264 RCParser::ParseType RCParser::parseLanguageResource() {
265   // Read LANGUAGE as an optional statement. If it's read correctly, we can
266   // upcast it to RCResource.
267   return parseLanguageStmt();
268 }
269 
270 RCParser::ParseType RCParser::parseAcceleratorsResource() {
271   ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements());
272   RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
273 
274   auto Accels = make_unique<AcceleratorsResource>(std::move(*OptStatements));
275 
276   while (!consumeOptionalType(Kind::BlockEnd)) {
277     ASSIGN_OR_RETURN(EventResult, readIntOrString());
278     RETURN_IF_ERROR(consumeType(Kind::Comma));
279     ASSIGN_OR_RETURN(IDResult, readInt());
280     ASSIGN_OR_RETURN(FlagsResult,
281                      parseFlags(AcceleratorsResource::Accelerator::OptionsStr));
282     Accels->addAccelerator(*EventResult, *IDResult, *FlagsResult);
283   }
284 
285   return std::move(Accels);
286 }
287 
288 RCParser::ParseType RCParser::parseCursorResource() {
289   ASSIGN_OR_RETURN(Arg, readString());
290   return make_unique<CursorResource>(*Arg);
291 }
292 
293 RCParser::ParseType RCParser::parseDialogResource(bool IsExtended) {
294   // Dialog resources have the following format of the arguments:
295   //  DIALOG:   x, y, width, height [opt stmts...] {controls...}
296   //  DIALOGEX: x, y, width, height [, helpID] [opt stmts...] {controls...}
297   // These are very similar, so we parse them together.
298   ASSIGN_OR_RETURN(LocResult, readIntsWithCommas(4, 4));
299 
300   uint32_t HelpID = 0; // When HelpID is unset, it's assumed to be 0.
301   if (IsExtended && consumeOptionalType(Kind::Comma)) {
302     ASSIGN_OR_RETURN(HelpIDResult, readInt());
303     HelpID = *HelpIDResult;
304   }
305 
306   ASSIGN_OR_RETURN(OptStatements,
307                    parseOptionalStatements(/*UseExtendedStmts = */ true));
308 
309   assert(isNextTokenKind(Kind::BlockBegin) &&
310          "parseOptionalStatements, when successful, halts on BlockBegin.");
311   consume();
312 
313   auto Dialog = make_unique<DialogResource>(
314       (*LocResult)[0], (*LocResult)[1], (*LocResult)[2], (*LocResult)[3],
315       HelpID, std::move(*OptStatements), IsExtended);
316 
317   while (!consumeOptionalType(Kind::BlockEnd)) {
318     ASSIGN_OR_RETURN(ControlDefResult, parseControl());
319     Dialog->addControl(std::move(*ControlDefResult));
320   }
321 
322   return std::move(Dialog);
323 }
324 
325 Expected<Control> RCParser::parseControl() {
326   // Each control definition (except CONTROL) follows one of the schemes below
327   // depending on the control class:
328   //  [class] text, id, x, y, width, height [, style] [, exstyle] [, helpID]
329   //  [class]       id, x, y, width, height [, style] [, exstyle] [, helpID]
330   // Note that control ids must be integers.
331   ASSIGN_OR_RETURN(ClassResult, readIdentifier());
332   StringRef ClassUpper = ClassResult->upper();
333   if (Control::SupportedCtls.find(ClassUpper) == Control::SupportedCtls.end())
334     return getExpectedError("control type, END or '}'", true);
335 
336   // Read caption if necessary.
337   StringRef Caption;
338   if (Control::CtlsWithTitle.find(ClassUpper) != Control::CtlsWithTitle.end()) {
339     ASSIGN_OR_RETURN(CaptionResult, readString());
340     RETURN_IF_ERROR(consumeType(Kind::Comma));
341     Caption = *CaptionResult;
342   }
343 
344   ASSIGN_OR_RETURN(Args, readIntsWithCommas(5, 8));
345 
346   auto TakeOptArg = [&Args](size_t Id) -> Optional<uint32_t> {
347     return Args->size() > Id ? (*Args)[Id] : Optional<uint32_t>();
348   };
349 
350   return Control(*ClassResult, Caption, (*Args)[0], (*Args)[1], (*Args)[2],
351                  (*Args)[3], (*Args)[4], TakeOptArg(5), TakeOptArg(6),
352                  TakeOptArg(7));
353 }
354 
355 RCParser::ParseType RCParser::parseIconResource() {
356   ASSIGN_OR_RETURN(Arg, readString());
357   return make_unique<IconResource>(*Arg);
358 }
359 
360 RCParser::ParseType RCParser::parseHTMLResource() {
361   ASSIGN_OR_RETURN(Arg, readString());
362   return make_unique<HTMLResource>(*Arg);
363 }
364 
365 RCParser::ParseType RCParser::parseMenuResource() {
366   ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements());
367   ASSIGN_OR_RETURN(Items, parseMenuItemsList());
368   return make_unique<MenuResource>(std::move(*OptStatements),
369                                    std::move(*Items));
370 }
371 
372 Expected<MenuDefinitionList> RCParser::parseMenuItemsList() {
373   RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
374 
375   MenuDefinitionList List;
376 
377   // Read a set of items. Each item is of one of three kinds:
378   //   MENUITEM SEPARATOR
379   //   MENUITEM caption:String, result:Int [, menu flags]...
380   //   POPUP caption:String [, menu flags]... { items... }
381   while (!consumeOptionalType(Kind::BlockEnd)) {
382     ASSIGN_OR_RETURN(ItemTypeResult, readIdentifier());
383 
384     bool IsMenuItem = ItemTypeResult->equals_lower("MENUITEM");
385     bool IsPopup = ItemTypeResult->equals_lower("POPUP");
386     if (!IsMenuItem && !IsPopup)
387       return getExpectedError("MENUITEM, POPUP, END or '}'", true);
388 
389     if (IsMenuItem && isNextTokenKind(Kind::Identifier)) {
390       // Now, expecting SEPARATOR.
391       ASSIGN_OR_RETURN(SeparatorResult, readIdentifier());
392       if (SeparatorResult->equals_lower("SEPARATOR")) {
393         List.addDefinition(make_unique<MenuSeparator>());
394         continue;
395       }
396 
397       return getExpectedError("SEPARATOR or string", true);
398     }
399 
400     // Not a separator. Read the caption.
401     ASSIGN_OR_RETURN(CaptionResult, readString());
402 
403     // If MENUITEM, expect also a comma and an integer.
404     uint32_t MenuResult = -1;
405 
406     if (IsMenuItem) {
407       RETURN_IF_ERROR(consumeType(Kind::Comma));
408       ASSIGN_OR_RETURN(IntResult, readInt());
409       MenuResult = *IntResult;
410     }
411 
412     ASSIGN_OR_RETURN(FlagsResult, parseFlags(MenuDefinition::OptionsStr));
413 
414     if (IsPopup) {
415       // If POPUP, read submenu items recursively.
416       ASSIGN_OR_RETURN(SubMenuResult, parseMenuItemsList());
417       List.addDefinition(make_unique<PopupItem>(*CaptionResult, *FlagsResult,
418                                                 std::move(*SubMenuResult)));
419       continue;
420     }
421 
422     assert(IsMenuItem);
423     List.addDefinition(
424         make_unique<MenuItem>(*CaptionResult, MenuResult, *FlagsResult));
425   }
426 
427   return std::move(List);
428 }
429 
430 RCParser::ParseType RCParser::parseStringTableResource() {
431   ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements());
432   RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
433 
434   auto Table = make_unique<StringTableResource>(std::move(*OptStatements));
435 
436   // Read strings until we reach the end of the block.
437   while (!consumeOptionalType(Kind::BlockEnd)) {
438     // Each definition consists of string's ID (an integer) and a string.
439     // Some examples in documentation suggest that there might be a comma in
440     // between, however we strictly adhere to the single statement definition.
441     ASSIGN_OR_RETURN(IDResult, readInt());
442     ASSIGN_OR_RETURN(StrResult, readString());
443     Table->addString(*IDResult, *StrResult);
444   }
445 
446   return std::move(Table);
447 }
448 
449 RCParser::ParseOptionType RCParser::parseLanguageStmt() {
450   ASSIGN_OR_RETURN(Args, readIntsWithCommas(/* min = */ 2, /* max = */ 2));
451   return make_unique<LanguageResource>((*Args)[0], (*Args)[1]);
452 }
453 
454 RCParser::ParseOptionType RCParser::parseCharacteristicsStmt() {
455   ASSIGN_OR_RETURN(Arg, readInt());
456   return make_unique<CharacteristicsStmt>(*Arg);
457 }
458 
459 RCParser::ParseOptionType RCParser::parseVersionStmt() {
460   ASSIGN_OR_RETURN(Arg, readInt());
461   return make_unique<VersionStmt>(*Arg);
462 }
463 
464 RCParser::ParseOptionType RCParser::parseCaptionStmt() {
465   ASSIGN_OR_RETURN(Arg, readString());
466   return make_unique<CaptionStmt>(*Arg);
467 }
468 
469 RCParser::ParseOptionType RCParser::parseFontStmt() {
470   ASSIGN_OR_RETURN(SizeResult, readInt());
471   RETURN_IF_ERROR(consumeType(Kind::Comma));
472   ASSIGN_OR_RETURN(NameResult, readString());
473   return make_unique<FontStmt>(*SizeResult, *NameResult);
474 }
475 
476 RCParser::ParseOptionType RCParser::parseStyleStmt() {
477   ASSIGN_OR_RETURN(Arg, readInt());
478   return make_unique<StyleStmt>(*Arg);
479 }
480 
481 Error RCParser::getExpectedError(const Twine Message, bool IsAlreadyRead) {
482   return make_error<ParserError>(
483       Message, IsAlreadyRead ? std::prev(CurLoc) : CurLoc, End);
484 }
485 
486 } // namespace rc
487 } // namespace llvm
488