blob: 41b11911b068f0ce3a64efc74d4f0779e1fbd89a [file] [log] [blame]
Marek Sokolowski5cd3d5c2017-08-18 18:24:17 +00001//===-- 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
28namespace llvm {
29namespace rc {
30
31RCParser::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
38char RCParser::ParserError::ID = 0;
39
40RCParser::RCParser(const std::vector<RCToken> &TokenList)
41 : Tokens(TokenList), CurLoc(Tokens.begin()), End(Tokens.end()) {}
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("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
Marek Sokolowski7f110522017-08-28 22:58:31 +000066 if (TypeToken->equalsLower("ACCELERATORS"))
67 Result = parseAcceleratorsResource();
68 else if (TypeToken->equalsLower("CURSOR"))
Marek Sokolowski72aa9372017-08-28 21:59:54 +000069 Result = parseCursorResource();
Marek Sokolowski4ac54d92017-08-29 16:49:59 +000070 else if (TypeToken->equalsLower("DIALOG"))
71 Result = parseDialogResource(false);
72 else if (TypeToken->equalsLower("DIALOGEX"))
73 Result = parseDialogResource(true);
Marek Sokolowski72aa9372017-08-28 21:59:54 +000074 else if (TypeToken->equalsLower("ICON"))
Marek Sokolowski5cd3d5c2017-08-18 18:24:17 +000075 Result = parseIconResource();
Marek Sokolowski72aa9372017-08-28 21:59:54 +000076 else if (TypeToken->equalsLower("HTML"))
77 Result = parseHTMLResource();
Marek Sokolowski99ecb0e2017-08-28 23:46:30 +000078 else if (TypeToken->equalsLower("MENU"))
79 Result = parseMenuResource();
Marek Sokolowski5cd3d5c2017-08-18 18:24:17 +000080 else
81 return getExpectedError("resource type", /* IsAlreadyRead = */ true);
82
83 if (Result)
84 (*Result)->setName(*NameToken);
85
86 return Result;
87}
88
89bool RCParser::isNextTokenKind(Kind TokenKind) const {
90 return !isEof() && look().kind() == TokenKind;
91}
92
93const RCToken &RCParser::look() const {
94 assert(!isEof());
95 return *CurLoc;
96}
97
98const RCToken &RCParser::read() {
99 assert(!isEof());
100 return *CurLoc++;
101}
102
103void RCParser::consume() {
104 assert(!isEof());
105 CurLoc++;
106}
107
108Expected<uint32_t> RCParser::readInt() {
109 if (!isNextTokenKind(Kind::Int))
110 return getExpectedError("integer");
111 return read().intValue();
112}
113
114Expected<StringRef> RCParser::readString() {
115 if (!isNextTokenKind(Kind::String))
116 return getExpectedError("string");
117 return read().value();
118}
119
120Expected<StringRef> RCParser::readIdentifier() {
121 if (!isNextTokenKind(Kind::Identifier))
122 return getExpectedError("identifier");
123 return read().value();
124}
125
Marek Sokolowski7f110522017-08-28 22:58:31 +0000126Expected<IntOrString> RCParser::readIntOrString() {
127 if (!isNextTokenKind(Kind::Int) && !isNextTokenKind(Kind::String))
128 return getExpectedError("int or string");
129 return IntOrString(read());
130}
131
Marek Sokolowski5cd3d5c2017-08-18 18:24:17 +0000132Expected<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");
Marek Sokolowski7f110522017-08-28 22:58:31 +0000137 return IntOrString(read());
Marek Sokolowski5cd3d5c2017-08-18 18:24:17 +0000138}
139
140Error 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
161bool RCParser::consumeOptionalType(Kind TokenKind) {
162 if (isNextTokenKind(TokenKind)) {
163 consume();
164 return true;
165 }
166
167 return false;
168}
169
170Expected<SmallVector<uint32_t, 8>>
171RCParser::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
Marek Sokolowski7f110522017-08-28 22:58:31 +0000202Expected<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
Marek Sokolowski5cd3d5c2017-08-18 18:24:17 +0000228// As for now, we ignore the extended set of statements.
229Expected<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
241Expected<std::unique_ptr<OptionalStmt>>
Marek Sokolowski4ac54d92017-08-29 16:49:59 +0000242RCParser::parseSingleOptionalStatement(bool IsExtended) {
Marek Sokolowski5cd3d5c2017-08-18 18:24:17 +0000243 ASSIGN_OR_RETURN(TypeToken, readIdentifier());
244 if (TypeToken->equals_lower("CHARACTERISTICS"))
245 return parseCharacteristicsStmt();
Marek Sokolowski4ac54d92017-08-29 16:49:59 +0000246 if (TypeToken->equals_lower("LANGUAGE"))
Marek Sokolowski5cd3d5c2017-08-18 18:24:17 +0000247 return parseLanguageStmt();
Marek Sokolowski4ac54d92017-08-29 16:49:59 +0000248 if (TypeToken->equals_lower("VERSION"))
Marek Sokolowski5cd3d5c2017-08-18 18:24:17 +0000249 return parseVersionStmt();
Marek Sokolowski4ac54d92017-08-29 16:49:59 +0000250
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);
Marek Sokolowski5cd3d5c2017-08-18 18:24:17 +0000262}
263
264RCParser::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
Marek Sokolowski7f110522017-08-28 22:58:31 +0000270RCParser::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
Marek Sokolowski72aa9372017-08-28 21:59:54 +0000288RCParser::ParseType RCParser::parseCursorResource() {
289 ASSIGN_OR_RETURN(Arg, readString());
290 return make_unique<CursorResource>(*Arg);
291}
292
Marek Sokolowski4ac54d92017-08-29 16:49:59 +0000293RCParser::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
325Expected<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());
Marek Sokolowski75fa1732017-08-29 20:03:18 +0000332 std::string ClassUpper = ClassResult->upper();
Marek Sokolowski4ac54d92017-08-29 16:49:59 +0000333 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
Marek Sokolowski5cd3d5c2017-08-18 18:24:17 +0000355RCParser::ParseType RCParser::parseIconResource() {
356 ASSIGN_OR_RETURN(Arg, readString());
357 return make_unique<IconResource>(*Arg);
358}
359
Marek Sokolowski72aa9372017-08-28 21:59:54 +0000360RCParser::ParseType RCParser::parseHTMLResource() {
361 ASSIGN_OR_RETURN(Arg, readString());
362 return make_unique<HTMLResource>(*Arg);
363}
364
Marek Sokolowski99ecb0e2017-08-28 23:46:30 +0000365RCParser::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
372Expected<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
Marek Sokolowski5cd3d5c2017-08-18 18:24:17 +0000430RCParser::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
449RCParser::ParseOptionType RCParser::parseLanguageStmt() {
450 ASSIGN_OR_RETURN(Args, readIntsWithCommas(/* min = */ 2, /* max = */ 2));
451 return make_unique<LanguageResource>((*Args)[0], (*Args)[1]);
452}
453
454RCParser::ParseOptionType RCParser::parseCharacteristicsStmt() {
455 ASSIGN_OR_RETURN(Arg, readInt());
456 return make_unique<CharacteristicsStmt>(*Arg);
457}
458
459RCParser::ParseOptionType RCParser::parseVersionStmt() {
460 ASSIGN_OR_RETURN(Arg, readInt());
461 return make_unique<VersionStmt>(*Arg);
462}
463
Marek Sokolowski4ac54d92017-08-29 16:49:59 +0000464RCParser::ParseOptionType RCParser::parseCaptionStmt() {
465 ASSIGN_OR_RETURN(Arg, readString());
466 return make_unique<CaptionStmt>(*Arg);
467}
468
469RCParser::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
476RCParser::ParseOptionType RCParser::parseStyleStmt() {
477 ASSIGN_OR_RETURN(Arg, readInt());
478 return make_unique<StyleStmt>(*Arg);
479}
480
Marek Sokolowski5cd3d5c2017-08-18 18:24:17 +0000481Error 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