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