blob: 57a30d14371536482d174f2dd1571c36e104d149 [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 Sokolowskifb74cb12017-09-28 22:41:38 +000080 else if (TypeToken->equalsLower("VERSIONINFO"))
81 Result = parseVersionInfoResource();
Marek Sokolowski5cd3d5c2017-08-18 18:24:17 +000082 else
83 return getExpectedError("resource type", /* IsAlreadyRead = */ true);
84
85 if (Result)
86 (*Result)->setName(*NameToken);
87
88 return Result;
89}
90
91bool RCParser::isNextTokenKind(Kind TokenKind) const {
92 return !isEof() && look().kind() == TokenKind;
93}
94
95const RCToken &RCParser::look() const {
96 assert(!isEof());
97 return *CurLoc;
98}
99
100const RCToken &RCParser::read() {
101 assert(!isEof());
102 return *CurLoc++;
103}
104
105void RCParser::consume() {
106 assert(!isEof());
107 CurLoc++;
108}
109
110Expected<uint32_t> RCParser::readInt() {
111 if (!isNextTokenKind(Kind::Int))
112 return getExpectedError("integer");
113 return read().intValue();
114}
115
116Expected<StringRef> RCParser::readString() {
117 if (!isNextTokenKind(Kind::String))
118 return getExpectedError("string");
119 return read().value();
120}
121
122Expected<StringRef> RCParser::readIdentifier() {
123 if (!isNextTokenKind(Kind::Identifier))
124 return getExpectedError("identifier");
125 return read().value();
126}
127
Marek Sokolowski7f110522017-08-28 22:58:31 +0000128Expected<IntOrString> RCParser::readIntOrString() {
129 if (!isNextTokenKind(Kind::Int) && !isNextTokenKind(Kind::String))
130 return getExpectedError("int or string");
131 return IntOrString(read());
132}
133
Marek Sokolowski5cd3d5c2017-08-18 18:24:17 +0000134Expected<IntOrString> RCParser::readTypeOrName() {
135 // We suggest that the correct resource name or type should be either an
136 // identifier or an integer. The original RC tool is much more liberal.
137 if (!isNextTokenKind(Kind::Identifier) && !isNextTokenKind(Kind::Int))
138 return getExpectedError("int or identifier");
Marek Sokolowski7f110522017-08-28 22:58:31 +0000139 return IntOrString(read());
Marek Sokolowski5cd3d5c2017-08-18 18:24:17 +0000140}
141
142Error RCParser::consumeType(Kind TokenKind) {
143 if (isNextTokenKind(TokenKind)) {
144 consume();
145 return Error::success();
146 }
147
148 switch (TokenKind) {
149#define TOKEN(TokenName) \
150 case Kind::TokenName: \
151 return getExpectedError(#TokenName);
152#define SHORT_TOKEN(TokenName, TokenCh) \
153 case Kind::TokenName: \
154 return getExpectedError(#TokenCh);
155#include "ResourceScriptTokenList.h"
156#undef SHORT_TOKEN
157#undef TOKEN
158 }
159
160 llvm_unreachable("All case options exhausted.");
161}
162
163bool RCParser::consumeOptionalType(Kind TokenKind) {
164 if (isNextTokenKind(TokenKind)) {
165 consume();
166 return true;
167 }
168
169 return false;
170}
171
172Expected<SmallVector<uint32_t, 8>>
173RCParser::readIntsWithCommas(size_t MinCount, size_t MaxCount) {
174 assert(MinCount <= MaxCount);
175
176 SmallVector<uint32_t, 8> Result;
177
178 auto FailureHandler =
179 [&](llvm::Error Err) -> Expected<SmallVector<uint32_t, 8>> {
180 if (Result.size() < MinCount)
181 return std::move(Err);
182 consumeError(std::move(Err));
183 return Result;
184 };
185
186 for (size_t i = 0; i < MaxCount; ++i) {
187 // Try to read a comma unless we read the first token.
188 // Sometimes RC tool requires them and sometimes not. We decide to
189 // always require them.
190 if (i >= 1) {
191 if (auto CommaError = consumeType(Kind::Comma))
192 return FailureHandler(std::move(CommaError));
193 }
194
195 if (auto IntResult = readInt())
196 Result.push_back(*IntResult);
197 else
198 return FailureHandler(IntResult.takeError());
199 }
200
201 return std::move(Result);
202}
203
Marek Sokolowski7f110522017-08-28 22:58:31 +0000204Expected<uint32_t> RCParser::parseFlags(ArrayRef<StringRef> FlagDesc) {
205 assert(FlagDesc.size() <= 32 && "More than 32 flags won't fit in result.");
206 assert(!FlagDesc.empty());
207
208 uint32_t Result = 0;
209 while (isNextTokenKind(Kind::Comma)) {
210 consume();
211 ASSIGN_OR_RETURN(FlagResult, readIdentifier());
212 bool FoundFlag = false;
213
214 for (size_t FlagId = 0; FlagId < FlagDesc.size(); ++FlagId) {
215 if (!FlagResult->equals_lower(FlagDesc[FlagId]))
216 continue;
217
218 Result |= (1U << FlagId);
219 FoundFlag = true;
220 break;
221 }
222
223 if (!FoundFlag)
224 return getExpectedError(join(FlagDesc, "/"), true);
225 }
226
227 return Result;
228}
229
Marek Sokolowski5cd3d5c2017-08-18 18:24:17 +0000230// As for now, we ignore the extended set of statements.
231Expected<OptionalStmtList> RCParser::parseOptionalStatements(bool IsExtended) {
232 OptionalStmtList Result;
233
234 // The last statement is always followed by the start of the block.
235 while (!isNextTokenKind(Kind::BlockBegin)) {
236 ASSIGN_OR_RETURN(SingleParse, parseSingleOptionalStatement(IsExtended));
237 Result.addStmt(std::move(*SingleParse));
238 }
239
240 return std::move(Result);
241}
242
243Expected<std::unique_ptr<OptionalStmt>>
Marek Sokolowski4ac54d92017-08-29 16:49:59 +0000244RCParser::parseSingleOptionalStatement(bool IsExtended) {
Marek Sokolowski5cd3d5c2017-08-18 18:24:17 +0000245 ASSIGN_OR_RETURN(TypeToken, readIdentifier());
246 if (TypeToken->equals_lower("CHARACTERISTICS"))
247 return parseCharacteristicsStmt();
Marek Sokolowski4ac54d92017-08-29 16:49:59 +0000248 if (TypeToken->equals_lower("LANGUAGE"))
Marek Sokolowski5cd3d5c2017-08-18 18:24:17 +0000249 return parseLanguageStmt();
Marek Sokolowski4ac54d92017-08-29 16:49:59 +0000250 if (TypeToken->equals_lower("VERSION"))
Marek Sokolowski5cd3d5c2017-08-18 18:24:17 +0000251 return parseVersionStmt();
Marek Sokolowski4ac54d92017-08-29 16:49:59 +0000252
253 if (IsExtended) {
254 if (TypeToken->equals_lower("CAPTION"))
255 return parseCaptionStmt();
256 if (TypeToken->equals_lower("FONT"))
257 return parseFontStmt();
258 if (TypeToken->equals_lower("STYLE"))
259 return parseStyleStmt();
260 }
261
262 return getExpectedError("optional statement type, BEGIN or '{'",
263 /* IsAlreadyRead = */ true);
Marek Sokolowski5cd3d5c2017-08-18 18:24:17 +0000264}
265
266RCParser::ParseType RCParser::parseLanguageResource() {
267 // Read LANGUAGE as an optional statement. If it's read correctly, we can
268 // upcast it to RCResource.
269 return parseLanguageStmt();
270}
271
Marek Sokolowski7f110522017-08-28 22:58:31 +0000272RCParser::ParseType RCParser::parseAcceleratorsResource() {
273 ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements());
274 RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
275
276 auto Accels = make_unique<AcceleratorsResource>(std::move(*OptStatements));
277
278 while (!consumeOptionalType(Kind::BlockEnd)) {
279 ASSIGN_OR_RETURN(EventResult, readIntOrString());
280 RETURN_IF_ERROR(consumeType(Kind::Comma));
281 ASSIGN_OR_RETURN(IDResult, readInt());
282 ASSIGN_OR_RETURN(FlagsResult,
283 parseFlags(AcceleratorsResource::Accelerator::OptionsStr));
284 Accels->addAccelerator(*EventResult, *IDResult, *FlagsResult);
285 }
286
287 return std::move(Accels);
288}
289
Marek Sokolowski72aa9372017-08-28 21:59:54 +0000290RCParser::ParseType RCParser::parseCursorResource() {
291 ASSIGN_OR_RETURN(Arg, readString());
292 return make_unique<CursorResource>(*Arg);
293}
294
Marek Sokolowski4ac54d92017-08-29 16:49:59 +0000295RCParser::ParseType RCParser::parseDialogResource(bool IsExtended) {
296 // Dialog resources have the following format of the arguments:
297 // DIALOG: x, y, width, height [opt stmts...] {controls...}
298 // DIALOGEX: x, y, width, height [, helpID] [opt stmts...] {controls...}
299 // These are very similar, so we parse them together.
300 ASSIGN_OR_RETURN(LocResult, readIntsWithCommas(4, 4));
301
302 uint32_t HelpID = 0; // When HelpID is unset, it's assumed to be 0.
303 if (IsExtended && consumeOptionalType(Kind::Comma)) {
304 ASSIGN_OR_RETURN(HelpIDResult, readInt());
305 HelpID = *HelpIDResult;
306 }
307
308 ASSIGN_OR_RETURN(OptStatements,
309 parseOptionalStatements(/*UseExtendedStmts = */ true));
310
311 assert(isNextTokenKind(Kind::BlockBegin) &&
312 "parseOptionalStatements, when successful, halts on BlockBegin.");
313 consume();
314
315 auto Dialog = make_unique<DialogResource>(
316 (*LocResult)[0], (*LocResult)[1], (*LocResult)[2], (*LocResult)[3],
317 HelpID, std::move(*OptStatements), IsExtended);
318
319 while (!consumeOptionalType(Kind::BlockEnd)) {
320 ASSIGN_OR_RETURN(ControlDefResult, parseControl());
321 Dialog->addControl(std::move(*ControlDefResult));
322 }
323
324 return std::move(Dialog);
325}
326
Marek Sokolowskifb74cb12017-09-28 22:41:38 +0000327RCParser::ParseType RCParser::parseVersionInfoResource() {
328 ASSIGN_OR_RETURN(FixedResult, parseVersionInfoFixed());
329 ASSIGN_OR_RETURN(BlockResult, parseVersionInfoBlockContents(StringRef()));
330 return make_unique<VersionInfoResource>(std::move(**BlockResult),
331 std::move(*FixedResult));
332}
333
Marek Sokolowski4ac54d92017-08-29 16:49:59 +0000334Expected<Control> RCParser::parseControl() {
335 // Each control definition (except CONTROL) follows one of the schemes below
336 // depending on the control class:
337 // [class] text, id, x, y, width, height [, style] [, exstyle] [, helpID]
338 // [class] id, x, y, width, height [, style] [, exstyle] [, helpID]
339 // Note that control ids must be integers.
340 ASSIGN_OR_RETURN(ClassResult, readIdentifier());
Marek Sokolowski75fa1732017-08-29 20:03:18 +0000341 std::string ClassUpper = ClassResult->upper();
Marek Sokolowski4ac54d92017-08-29 16:49:59 +0000342 if (Control::SupportedCtls.find(ClassUpper) == Control::SupportedCtls.end())
343 return getExpectedError("control type, END or '}'", true);
344
345 // Read caption if necessary.
346 StringRef Caption;
347 if (Control::CtlsWithTitle.find(ClassUpper) != Control::CtlsWithTitle.end()) {
348 ASSIGN_OR_RETURN(CaptionResult, readString());
349 RETURN_IF_ERROR(consumeType(Kind::Comma));
350 Caption = *CaptionResult;
351 }
352
353 ASSIGN_OR_RETURN(Args, readIntsWithCommas(5, 8));
354
355 auto TakeOptArg = [&Args](size_t Id) -> Optional<uint32_t> {
356 return Args->size() > Id ? (*Args)[Id] : Optional<uint32_t>();
357 };
358
359 return Control(*ClassResult, Caption, (*Args)[0], (*Args)[1], (*Args)[2],
360 (*Args)[3], (*Args)[4], TakeOptArg(5), TakeOptArg(6),
361 TakeOptArg(7));
362}
363
Marek Sokolowski5cd3d5c2017-08-18 18:24:17 +0000364RCParser::ParseType RCParser::parseIconResource() {
365 ASSIGN_OR_RETURN(Arg, readString());
366 return make_unique<IconResource>(*Arg);
367}
368
Marek Sokolowski72aa9372017-08-28 21:59:54 +0000369RCParser::ParseType RCParser::parseHTMLResource() {
370 ASSIGN_OR_RETURN(Arg, readString());
371 return make_unique<HTMLResource>(*Arg);
372}
373
Marek Sokolowski99ecb0e2017-08-28 23:46:30 +0000374RCParser::ParseType RCParser::parseMenuResource() {
375 ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements());
376 ASSIGN_OR_RETURN(Items, parseMenuItemsList());
377 return make_unique<MenuResource>(std::move(*OptStatements),
378 std::move(*Items));
379}
380
381Expected<MenuDefinitionList> RCParser::parseMenuItemsList() {
382 RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
383
384 MenuDefinitionList List;
385
386 // Read a set of items. Each item is of one of three kinds:
387 // MENUITEM SEPARATOR
388 // MENUITEM caption:String, result:Int [, menu flags]...
389 // POPUP caption:String [, menu flags]... { items... }
390 while (!consumeOptionalType(Kind::BlockEnd)) {
391 ASSIGN_OR_RETURN(ItemTypeResult, readIdentifier());
392
393 bool IsMenuItem = ItemTypeResult->equals_lower("MENUITEM");
394 bool IsPopup = ItemTypeResult->equals_lower("POPUP");
395 if (!IsMenuItem && !IsPopup)
396 return getExpectedError("MENUITEM, POPUP, END or '}'", true);
397
398 if (IsMenuItem && isNextTokenKind(Kind::Identifier)) {
399 // Now, expecting SEPARATOR.
400 ASSIGN_OR_RETURN(SeparatorResult, readIdentifier());
401 if (SeparatorResult->equals_lower("SEPARATOR")) {
402 List.addDefinition(make_unique<MenuSeparator>());
403 continue;
404 }
405
406 return getExpectedError("SEPARATOR or string", true);
407 }
408
409 // Not a separator. Read the caption.
410 ASSIGN_OR_RETURN(CaptionResult, readString());
411
412 // If MENUITEM, expect also a comma and an integer.
413 uint32_t MenuResult = -1;
414
415 if (IsMenuItem) {
416 RETURN_IF_ERROR(consumeType(Kind::Comma));
417 ASSIGN_OR_RETURN(IntResult, readInt());
418 MenuResult = *IntResult;
419 }
420
421 ASSIGN_OR_RETURN(FlagsResult, parseFlags(MenuDefinition::OptionsStr));
422
423 if (IsPopup) {
424 // If POPUP, read submenu items recursively.
425 ASSIGN_OR_RETURN(SubMenuResult, parseMenuItemsList());
426 List.addDefinition(make_unique<PopupItem>(*CaptionResult, *FlagsResult,
427 std::move(*SubMenuResult)));
428 continue;
429 }
430
431 assert(IsMenuItem);
432 List.addDefinition(
433 make_unique<MenuItem>(*CaptionResult, MenuResult, *FlagsResult));
434 }
435
436 return std::move(List);
437}
438
Marek Sokolowski5cd3d5c2017-08-18 18:24:17 +0000439RCParser::ParseType RCParser::parseStringTableResource() {
440 ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements());
441 RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
442
443 auto Table = make_unique<StringTableResource>(std::move(*OptStatements));
444
445 // Read strings until we reach the end of the block.
446 while (!consumeOptionalType(Kind::BlockEnd)) {
447 // Each definition consists of string's ID (an integer) and a string.
448 // Some examples in documentation suggest that there might be a comma in
449 // between, however we strictly adhere to the single statement definition.
450 ASSIGN_OR_RETURN(IDResult, readInt());
451 ASSIGN_OR_RETURN(StrResult, readString());
452 Table->addString(*IDResult, *StrResult);
453 }
454
455 return std::move(Table);
456}
457
Marek Sokolowskifb74cb12017-09-28 22:41:38 +0000458Expected<std::unique_ptr<VersionInfoBlock>>
459RCParser::parseVersionInfoBlockContents(StringRef BlockName) {
460 RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
461
462 auto Contents = make_unique<VersionInfoBlock>(BlockName);
463
464 while (!isNextTokenKind(Kind::BlockEnd)) {
465 ASSIGN_OR_RETURN(Stmt, parseVersionInfoStmt());
466 Contents->addStmt(std::move(*Stmt));
467 }
468
469 consume(); // Consume BlockEnd.
470
471 return std::move(Contents);
472}
473
474Expected<std::unique_ptr<VersionInfoStmt>> RCParser::parseVersionInfoStmt() {
475 // Expect either BLOCK or VALUE, then a name or a key (a string).
476 ASSIGN_OR_RETURN(TypeResult, readIdentifier());
477
478 if (TypeResult->equals_lower("BLOCK")) {
479 ASSIGN_OR_RETURN(NameResult, readString());
480 return parseVersionInfoBlockContents(*NameResult);
481 }
482
483 if (TypeResult->equals_lower("VALUE")) {
484 ASSIGN_OR_RETURN(KeyResult, readString());
485 // Read a (possibly empty) list of strings and/or ints, each preceded by
486 // a comma.
487 std::vector<IntOrString> Values;
488
489 while (consumeOptionalType(Kind::Comma)) {
490 ASSIGN_OR_RETURN(ValueResult, readIntOrString());
491 Values.push_back(*ValueResult);
492 }
493 return make_unique<VersionInfoValue>(*KeyResult, std::move(Values));
494 }
495
496 return getExpectedError("BLOCK or VALUE", true);
497}
498
499Expected<VersionInfoResource::VersionInfoFixed>
500RCParser::parseVersionInfoFixed() {
501 using RetType = VersionInfoResource::VersionInfoFixed;
502 RetType Result;
503
504 // Read until the beginning of the block.
505 while (!isNextTokenKind(Kind::BlockBegin)) {
506 ASSIGN_OR_RETURN(TypeResult, readIdentifier());
507 auto FixedType = RetType::getFixedType(*TypeResult);
508
509 if (!RetType::isTypeSupported(FixedType))
510 return getExpectedError("fixed VERSIONINFO statement type", true);
511 if (Result.IsTypePresent[FixedType])
512 return getExpectedError("yet unread fixed VERSIONINFO statement type",
513 true);
514
515 // VERSION variations take multiple integers.
516 size_t NumInts = RetType::isVersionType(FixedType) ? 4 : 1;
517 ASSIGN_OR_RETURN(ArgsResult, readIntsWithCommas(NumInts, NumInts));
518 Result.setValue(FixedType, *ArgsResult);
519 }
520
521 return Result;
522}
523
Marek Sokolowski5cd3d5c2017-08-18 18:24:17 +0000524RCParser::ParseOptionType RCParser::parseLanguageStmt() {
525 ASSIGN_OR_RETURN(Args, readIntsWithCommas(/* min = */ 2, /* max = */ 2));
526 return make_unique<LanguageResource>((*Args)[0], (*Args)[1]);
527}
528
529RCParser::ParseOptionType RCParser::parseCharacteristicsStmt() {
530 ASSIGN_OR_RETURN(Arg, readInt());
531 return make_unique<CharacteristicsStmt>(*Arg);
532}
533
534RCParser::ParseOptionType RCParser::parseVersionStmt() {
535 ASSIGN_OR_RETURN(Arg, readInt());
536 return make_unique<VersionStmt>(*Arg);
537}
538
Marek Sokolowski4ac54d92017-08-29 16:49:59 +0000539RCParser::ParseOptionType RCParser::parseCaptionStmt() {
540 ASSIGN_OR_RETURN(Arg, readString());
541 return make_unique<CaptionStmt>(*Arg);
542}
543
544RCParser::ParseOptionType RCParser::parseFontStmt() {
545 ASSIGN_OR_RETURN(SizeResult, readInt());
546 RETURN_IF_ERROR(consumeType(Kind::Comma));
547 ASSIGN_OR_RETURN(NameResult, readString());
548 return make_unique<FontStmt>(*SizeResult, *NameResult);
549}
550
551RCParser::ParseOptionType RCParser::parseStyleStmt() {
552 ASSIGN_OR_RETURN(Arg, readInt());
553 return make_unique<StyleStmt>(*Arg);
554}
555
Marek Sokolowski5cd3d5c2017-08-18 18:24:17 +0000556Error RCParser::getExpectedError(const Twine Message, bool IsAlreadyRead) {
557 return make_error<ParserError>(
558 Message, IsAlreadyRead ? std::prev(CurLoc) : CurLoc, End);
559}
560
561} // namespace rc
562} // namespace llvm