1 /* 2 Copyright (C) 2010-2014 Kristian Duske 3 4 This file is part of TrenchBroom. 5 6 TrenchBroom is free software: you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 TrenchBroom is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with TrenchBroom. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 #include "FgdParser.h" 21 22 #include "CollectionUtils.h" 23 #include "Exceptions.h" 24 #include "Assets/EntityDefinition.h" 25 #include "Assets/AttributeDefinition.h" 26 #include "Assets/ModelDefinition.h" 27 #include "IO/ParserStatus.h" 28 29 namespace TrenchBroom { 30 namespace IO { FgdTokenizer(const char * begin,const char * end)31 FgdTokenizer::FgdTokenizer(const char* begin, const char* end) : 32 Tokenizer(begin, end) {} 33 FgdTokenizer(const String & str)34 FgdTokenizer::FgdTokenizer(const String& str) : 35 Tokenizer(str) {} 36 37 const String FgdTokenizer::WordDelims = " \t\n\r()[]?;:,="; 38 emitToken()39 FgdTokenizer::Token FgdTokenizer::emitToken() { 40 while (!eof()) { 41 size_t startLine = line(); 42 size_t startColumn = column(); 43 const char* c = curPos(); 44 45 switch (*c) { 46 case '/': 47 advance(); 48 if (curChar() == '/') 49 discardUntil("\n\r"); 50 break; 51 case '(': 52 advance(); 53 return Token(FgdToken::OParenthesis, c, c+1, offset(c), startLine, startColumn); 54 case ')': 55 advance(); 56 return Token(FgdToken::CParenthesis, c, c+1, offset(c), startLine, startColumn); 57 case '[': 58 advance(); 59 return Token(FgdToken::OBracket, c, c+1, offset(c), startLine, startColumn); 60 case ']': 61 advance(); 62 return Token(FgdToken::CBracket, c, c+1, offset(c), startLine, startColumn); 63 case '=': 64 advance(); 65 return Token(FgdToken::Equality, c, c+1, offset(c), startLine, startColumn); 66 case ',': 67 advance(); 68 return Token(FgdToken::Comma, c, c+1, offset(c), startLine, startColumn); 69 case ':': 70 advance(); 71 return Token(FgdToken::Colon, c, c+1, offset(c), startLine, startColumn); 72 case '"': { // quoted string 73 advance(); 74 c = curPos(); 75 const char* e = readQuotedString(); 76 return Token(FgdToken::String, c, e, offset(c), startLine, startColumn); 77 } 78 case ' ': 79 case '\t': 80 case '\n': 81 case '\r': 82 discardWhile(Whitespace()); 83 break; 84 default: { 85 const char* e = readInteger(WordDelims); 86 if (e != NULL) 87 return Token(FgdToken::Integer, c, e, offset(c), startLine, startColumn); 88 89 e = readDecimal(WordDelims); 90 if (e != NULL) 91 return Token(FgdToken::Decimal, c, e, offset(c), startLine, startColumn); 92 93 e = readString(WordDelims); 94 if (e == NULL) 95 throw ParserException(startLine, startColumn, "Unexpected character: '" + String(c, 1) + "'"); 96 return Token(FgdToken::Word, c, e, offset(c), startLine, startColumn); 97 } 98 } 99 } 100 return Token(FgdToken::Eof, NULL, NULL, length(), line(), column()); 101 } 102 FgdParser(const char * begin,const char * end,const Color & defaultEntityColor)103 FgdParser::FgdParser(const char* begin, const char* end, const Color& defaultEntityColor) : 104 m_defaultEntityColor(defaultEntityColor), 105 m_tokenizer(FgdTokenizer(begin, end)) {} 106 FgdParser(const String & str,const Color & defaultEntityColor)107 FgdParser::FgdParser(const String& str, const Color& defaultEntityColor) : 108 m_defaultEntityColor(defaultEntityColor), 109 m_tokenizer(FgdTokenizer(str)) {} 110 tokenNames() const111 FgdParser::TokenNameMap FgdParser::tokenNames() const { 112 using namespace FgdToken; 113 114 TokenNameMap names; 115 names[Integer] = "integer"; 116 names[Decimal] = "decimal"; 117 names[Word] = "word"; 118 names[String] = "string"; 119 names[OParenthesis] = "'('"; 120 names[CParenthesis] = "')'"; 121 names[OBracket] = "'['"; 122 names[CBracket] = "']'"; 123 names[Equality] = "'='"; 124 names[Colon] = "':'"; 125 names[Comma] = "','"; 126 names[Eof] = "end of file"; 127 return names; 128 } 129 doParseDefinitions(ParserStatus & status)130 Assets::EntityDefinitionList FgdParser::doParseDefinitions(ParserStatus& status) { 131 Assets::EntityDefinitionList definitions; 132 try { 133 Assets::EntityDefinition* definition = parseDefinition(status); 134 status.progress(m_tokenizer.progress()); 135 while (definition != NULL) { 136 definitions.push_back(definition); 137 definition = parseDefinition(status); 138 status.progress(m_tokenizer.progress()); 139 } 140 return definitions; 141 } catch (...) { 142 VectorUtils::clearAndDelete(definitions); 143 throw; 144 } 145 } 146 parseDefinition(ParserStatus & status)147 Assets::EntityDefinition* FgdParser::parseDefinition(ParserStatus& status) { 148 Token token = m_tokenizer.nextToken(); 149 if (token.type() == FgdToken::Eof) 150 return NULL; 151 152 const String classname = token.data(); 153 if (StringUtils::caseInsensitiveEqual(classname, "@SolidClass")) { 154 return parseSolidClass(status); 155 } else if (StringUtils::caseInsensitiveEqual(classname, "@PointClass")) { 156 return parsePointClass(status); 157 } else if (StringUtils::caseInsensitiveEqual(classname, "@BaseClass")) { 158 const EntityDefinitionClassInfo baseClass = parseBaseClass(status); 159 m_baseClasses[baseClass.name()] = baseClass; 160 return parseDefinition(status); 161 } else if (StringUtils::caseInsensitiveEqual(classname, "@Main")) { 162 skipMainClass(status); 163 return parseDefinition(status); 164 } else { 165 const String msg = "Unknown entity definition class '" + classname + "'"; 166 status.error(token.line(), token.column(), msg); 167 throw ParserException(token.line(), token.column(), msg); 168 } 169 } 170 parseSolidClass(ParserStatus & status)171 Assets::EntityDefinition* FgdParser::parseSolidClass(ParserStatus& status) { 172 EntityDefinitionClassInfo classInfo = parseClass(status); 173 if (classInfo.hasSize()) 174 status.warn(classInfo.line(), classInfo.column(), "Solid entity definition must not have a size"); 175 if (!classInfo.models().empty()) 176 status.warn(classInfo.line(), classInfo.column(), "Solid entity definition must not have model definitions"); 177 return new Assets::BrushEntityDefinition(classInfo.name(), classInfo.color(), classInfo.description(), classInfo.attributeList()); 178 } 179 parsePointClass(ParserStatus & status)180 Assets::EntityDefinition* FgdParser::parsePointClass(ParserStatus& status) { 181 EntityDefinitionClassInfo classInfo = parseClass(status); 182 return new Assets::PointEntityDefinition(classInfo.name(), classInfo.color(), classInfo.size(), classInfo.description(), classInfo.attributeList(), classInfo.models()); 183 } 184 parseBaseClass(ParserStatus & status)185 EntityDefinitionClassInfo FgdParser::parseBaseClass(ParserStatus& status) { 186 EntityDefinitionClassInfo classInfo = parseClass(status); 187 if (m_baseClasses.count(classInfo.name()) > 0) 188 status.warn(classInfo.line(), classInfo.column(), "Redefinition of base class '" + classInfo.name() + "'"); 189 return classInfo; 190 } 191 parseClass(ParserStatus & status)192 EntityDefinitionClassInfo FgdParser::parseClass(ParserStatus& status) { 193 Token token; 194 expect(status, FgdToken::Word | FgdToken::Equality, token = m_tokenizer.nextToken()); 195 196 StringList superClasses; 197 EntityDefinitionClassInfo classInfo(token.line(), token.column(), m_defaultEntityColor); 198 199 while (token.type() == FgdToken::Word) { 200 const String typeName = token.data(); 201 if (StringUtils::caseInsensitiveEqual(typeName, "base")) { 202 if (!superClasses.empty()) 203 status.warn(token.line(), token.column(), "Found multiple base attributes"); 204 superClasses = parseSuperClasses(status); 205 } else if (StringUtils::caseInsensitiveEqual(typeName, "color")) { 206 if (classInfo.hasColor()) 207 status.warn(token.line(), token.column(), "Found multiple color attributes"); 208 classInfo.setColor(parseColor(status)); 209 } else if (StringUtils::caseInsensitiveEqual(typeName, "size")) { 210 if (classInfo.hasSize()) 211 status.warn(token.line(), token.column(), "Found multiple size attributes"); 212 classInfo.setSize(parseSize(status)); 213 } else if (StringUtils::caseInsensitiveEqual(typeName, "model") || 214 StringUtils::caseInsensitiveEqual(typeName, "studio")) { 215 if (!classInfo.models().empty()) 216 status.warn(token.line(), token.column(), "Found multiple model attributes"); 217 classInfo.addModelDefinitions(parseModels(status)); 218 } else { 219 status.warn(token.line(), token.column(), "Unknown entity definition header attribute '" + typeName + "'"); 220 skipClassAttribute(status); 221 } 222 expect(status, FgdToken::Equality | FgdToken::Word, token = m_tokenizer.nextToken()); 223 } 224 225 expect(status, FgdToken::Word, token = m_tokenizer.nextToken()); 226 classInfo.setName(token.data()); 227 228 expect(status, FgdToken::Colon | FgdToken::OBracket, token = m_tokenizer.nextToken()); 229 if (token.type() == FgdToken::Colon) { 230 expect(status, FgdToken::String, token = m_tokenizer.nextToken()); 231 classInfo.setDescription(StringUtils::trim(token.data())); 232 } else { 233 m_tokenizer.pushToken(token); 234 } 235 236 classInfo.addAttributeDefinitions(parseProperties(status)); 237 classInfo.resolveBaseClasses(m_baseClasses, superClasses); 238 return classInfo; 239 } 240 skipMainClass(ParserStatus & status)241 void FgdParser::skipMainClass(ParserStatus& status) { 242 Token token; 243 expect(status, FgdToken::Equality, token = m_tokenizer.nextToken()); 244 expect(status, FgdToken::OBracket, token = m_tokenizer.nextToken()); 245 do { 246 token = m_tokenizer.nextToken(); 247 } while (token.type() != FgdToken::CBracket); 248 } 249 parseSuperClasses(ParserStatus & status)250 StringList FgdParser::parseSuperClasses(ParserStatus& status) { 251 StringList superClasses; 252 Token token; 253 expect(status, FgdToken::OParenthesis, token = m_tokenizer.nextToken()); 254 expect(status, FgdToken::Word | FgdToken::CParenthesis, token = m_tokenizer.nextToken()); 255 if (token.type() == FgdToken::Word) { 256 m_tokenizer.pushToken(token); 257 do { 258 expect(status, FgdToken::Word, token = m_tokenizer.nextToken()); 259 superClasses.push_back(token.data()); 260 expect(status, FgdToken::Comma | FgdToken::CParenthesis, token = m_tokenizer.nextToken()); 261 } while (token.type() == FgdToken::Comma); 262 } 263 return superClasses; 264 } 265 parseModels(ParserStatus & status)266 Assets::ModelDefinitionList FgdParser::parseModels(ParserStatus& status) { 267 Assets::ModelDefinitionList result; 268 Token token; 269 expect(status, FgdToken::OParenthesis, token = m_tokenizer.nextToken()); 270 expect(status, FgdToken::String | FgdToken::Word | FgdToken::CParenthesis, token = m_tokenizer.nextToken()); 271 if (token.type() == FgdToken::String || token.type() == FgdToken::Word) { 272 m_tokenizer.pushToken(token); 273 do { 274 expect(status, FgdToken::String | FgdToken::Word, token = m_tokenizer.peekToken()); 275 if (token.type() == FgdToken::String) 276 result.push_back(parseStaticModel(status)); 277 else 278 result.push_back(parseDynamicModel(status)); 279 expect(status, FgdToken::Comma | FgdToken::CParenthesis, token = m_tokenizer.nextToken()); 280 } while (token.type() == FgdToken::Comma); 281 } 282 return result; 283 } 284 parseStaticModel(ParserStatus & status)285 Assets::ModelDefinitionPtr FgdParser::parseStaticModel(ParserStatus& status) { 286 Token token; 287 expect(status, FgdToken::String, token = m_tokenizer.nextToken()); 288 const String pathStr = token.data(); 289 const IO::Path path(!pathStr.empty() && pathStr[0] == ':' ? pathStr.substr(1) : pathStr); 290 291 std::vector<size_t> indices; 292 293 expect(status, FgdToken::Integer | FgdToken::Word | FgdToken::Comma | FgdToken::CParenthesis, token = m_tokenizer.nextToken()); 294 if (token.type() == FgdToken::Integer) { 295 indices.push_back(token.toInteger<size_t>()); 296 expect(status, FgdToken::Integer | FgdToken::Word | FgdToken::Comma | FgdToken::CParenthesis, token = m_tokenizer.nextToken()); 297 if (token.type() == FgdToken::Integer) { 298 indices.push_back(token.toInteger<size_t>()); 299 expect(status, FgdToken::Word | FgdToken::Comma | FgdToken::CParenthesis, token = m_tokenizer.nextToken()); 300 } 301 } 302 303 size_t skinIndex = 0; 304 size_t frameIndex = 0; 305 if (!indices.empty()) { 306 skinIndex = indices[0]; 307 if (indices.size() > 1) 308 frameIndex = indices[1]; 309 } 310 311 if (token.type() == FgdToken::Word) { 312 const String attributeKey = token.data(); 313 expect(status, FgdToken::Equality, token = m_tokenizer.nextToken()); 314 expect(status, FgdToken::String | FgdToken::Integer, token = m_tokenizer.nextToken()); 315 if (token.type() == FgdToken::String) { 316 const String attributeValue = token.data(); 317 return Assets::ModelDefinitionPtr(new Assets::StaticModelDefinition(path, skinIndex, frameIndex, attributeKey, attributeValue)); 318 } else { 319 const int flagValue = token.toInteger<int>(); 320 return Assets::ModelDefinitionPtr(new Assets::StaticModelDefinition(path, skinIndex, frameIndex, attributeKey, flagValue)); 321 } 322 } else { 323 m_tokenizer.pushToken(token); 324 return Assets::ModelDefinitionPtr(new Assets::StaticModelDefinition(path, skinIndex, frameIndex)); 325 } 326 } 327 parseDynamicModel(ParserStatus & status)328 Assets::ModelDefinitionPtr FgdParser::parseDynamicModel(ParserStatus& status) { 329 Token token; 330 String pathKey, skinKey, frameKey; 331 332 expect(status, FgdToken::Word, token = m_tokenizer.nextToken()); 333 if (!StringUtils::caseInsensitiveEqual("pathKey", token.data())) { 334 const String msg = "Expected 'pathKey', but found '" + token.data() + "'"; 335 status.error(token.line(), token.column(), msg); 336 throw ParserException(token.line(), token.column(), msg); 337 } 338 339 expect(status, FgdToken::Equality, token = m_tokenizer.nextToken()); 340 expect(status, FgdToken::String, token = m_tokenizer.nextToken()); 341 pathKey = token.data(); 342 343 expect(status, FgdToken::Word | FgdToken::Comma | FgdToken::CParenthesis, token = m_tokenizer.nextToken()); 344 while (token.type() == FgdToken::Word) { 345 if (StringUtils::caseInsensitiveEqual("skinKey", token.data())) { 346 m_tokenizer.pushToken(token); 347 skinKey = parseNamedValue(status, "skinKey"); 348 } else if (StringUtils::caseInsensitiveEqual("frameKey", token.data())) { 349 m_tokenizer.pushToken(token); 350 frameKey = parseNamedValue(status, "frameKey"); 351 } else { 352 const String msg = "Expected 'skinKey' or 'frameKey', but found '" + token.data() + "'"; 353 status.error(token.line(), token.column(), msg); 354 throw ParserException(token.line(), token.column(), msg); 355 } 356 expect(status, FgdToken::Word | FgdToken::Comma | FgdToken::CParenthesis, token = m_tokenizer.nextToken()); 357 } 358 m_tokenizer.pushToken(token); 359 360 return Assets::ModelDefinitionPtr(new Assets::DynamicModelDefinition(pathKey, skinKey, frameKey)); 361 } 362 parseNamedValue(ParserStatus & status,const String & name)363 String FgdParser::parseNamedValue(ParserStatus& status, const String& name) { 364 Token token; 365 expect(status, FgdToken::Word, token = m_tokenizer.nextToken()); 366 assert(StringUtils::caseInsensitiveEqual(name, token.data())); 367 expect(status, FgdToken::Equality, token = m_tokenizer.nextToken()); 368 expect(status, FgdToken::String, token = m_tokenizer.nextToken()); 369 return token.data(); 370 } 371 skipClassAttribute(ParserStatus & status)372 void FgdParser::skipClassAttribute(ParserStatus& status) { 373 size_t depth = 0; 374 Token token; 375 do { 376 token = m_tokenizer.nextToken(); 377 if (token.type() == FgdToken::OParenthesis) 378 ++depth; 379 else if (token.type() == FgdToken::CParenthesis) 380 --depth; 381 } while (depth > 0 && token.type() != FgdToken::Eof); 382 } 383 parseProperties(ParserStatus & status)384 Assets::AttributeDefinitionMap FgdParser::parseProperties(ParserStatus& status) { 385 Assets::AttributeDefinitionMap attributes; 386 387 Token token; 388 expect(status, FgdToken::OBracket, token = m_tokenizer.nextToken()); 389 expect(status, FgdToken::Word | FgdToken::CBracket, token = m_tokenizer.nextToken()); 390 while (token.type() != FgdToken::CBracket) { 391 const String attributeKey = token.data(); 392 393 if (attributes.count(attributeKey) > 0) { 394 status.warn(token.line(), token.column(), "Redefinition of property declaration '" + attributeKey + "'"); 395 } 396 397 expect(status, FgdToken::OParenthesis, token = m_tokenizer.nextToken()); 398 expect(status, FgdToken::Word, token = m_tokenizer.nextToken()); 399 const String typeName = token.data(); 400 expect(status, FgdToken::CParenthesis, token = m_tokenizer.nextToken()); 401 402 if (StringUtils::caseInsensitiveEqual(typeName, "target_source")) { 403 attributes[attributeKey] = parseTargetSourceAttribute(status, attributeKey); 404 } else if (StringUtils::caseInsensitiveEqual(typeName, "target_destination")) { 405 attributes[attributeKey] = parseTargetDestinationAttribute(status, attributeKey); 406 } else if (StringUtils::caseInsensitiveEqual(typeName, "string")) { 407 attributes[attributeKey] = parseStringAttribute(status, attributeKey); 408 } else if (StringUtils::caseInsensitiveEqual(typeName, "integer")) { 409 attributes[attributeKey] = parseIntegerAttribute(status, attributeKey); 410 } else if (StringUtils::caseInsensitiveEqual(typeName, "float")) { 411 attributes[attributeKey] = parseFloatAttribute(status, attributeKey); 412 } else if (StringUtils::caseInsensitiveEqual(typeName, "choices")) { 413 attributes[attributeKey] = parseChoicesAttribute(status, attributeKey); 414 } else if (StringUtils::caseInsensitiveEqual(typeName, "flags")) { 415 attributes[attributeKey] = parseFlagsAttribute(status, attributeKey); 416 } else { 417 status.debug(token.line(), token.column(), "Unknown property definition type '" + typeName + "' for attribute '" + attributeKey + "'"); 418 attributes[attributeKey] = parseUnknownAttribute(status, attributeKey); 419 } 420 421 expect(status, FgdToken::Word | FgdToken::CBracket, token = m_tokenizer.nextToken()); 422 } 423 424 return attributes; 425 } 426 parseTargetSourceAttribute(ParserStatus & status,const String & name)427 Assets::AttributeDefinitionPtr FgdParser::parseTargetSourceAttribute(ParserStatus& status, const String& name) { 428 const String shortDescription = parseAttributeDescription(status); 429 parseDefaultStringValue(status); 430 const String longDescription = parseAttributeDescription(status); 431 return Assets::AttributeDefinitionPtr(new Assets::AttributeDefinition(name, Assets::AttributeDefinition::Type_TargetSourceAttribute, shortDescription, longDescription)); 432 } 433 parseTargetDestinationAttribute(ParserStatus & status,const String & name)434 Assets::AttributeDefinitionPtr FgdParser::parseTargetDestinationAttribute(ParserStatus& status, const String& name) { 435 const String shortDescription = parseAttributeDescription(status); 436 parseDefaultStringValue(status); 437 const String longDescription = parseAttributeDescription(status); 438 return Assets::AttributeDefinitionPtr(new Assets::AttributeDefinition(name, Assets::AttributeDefinition::Type_TargetDestinationAttribute, shortDescription, longDescription)); 439 } 440 parseStringAttribute(ParserStatus & status,const String & name)441 Assets::AttributeDefinitionPtr FgdParser::parseStringAttribute(ParserStatus& status, const String& name) { 442 const String shortDescription = parseAttributeDescription(status); 443 const DefaultValue<String> defaultValue = parseDefaultStringValue(status); 444 const String longDescription = parseAttributeDescription(status); 445 446 if (defaultValue.present) 447 return Assets::AttributeDefinitionPtr(new Assets::StringAttributeDefinition(name, shortDescription, longDescription, defaultValue.value)); 448 return Assets::AttributeDefinitionPtr(new Assets::StringAttributeDefinition(name, shortDescription, longDescription)); 449 } 450 parseIntegerAttribute(ParserStatus & status,const String & name)451 Assets::AttributeDefinitionPtr FgdParser::parseIntegerAttribute(ParserStatus& status, const String& name) { 452 const String shortDescription = parseAttributeDescription(status); 453 const DefaultValue<int> defaultValue = parseDefaultIntegerValue(status); 454 const String longDescription = parseAttributeDescription(status); 455 456 if (defaultValue.present) 457 return Assets::AttributeDefinitionPtr(new Assets::IntegerAttributeDefinition(name, shortDescription, longDescription, defaultValue.value)); 458 return Assets::AttributeDefinitionPtr(new Assets::IntegerAttributeDefinition(name, shortDescription, longDescription)); 459 } 460 parseFloatAttribute(ParserStatus & status,const String & name)461 Assets::AttributeDefinitionPtr FgdParser::parseFloatAttribute(ParserStatus& status, const String& name) { 462 const String shortDescription = parseAttributeDescription(status); 463 const DefaultValue<float> defaultValue = parseDefaultFloatValue(status); 464 const String longDescription = parseAttributeDescription(status); 465 466 if (defaultValue.present) 467 return Assets::AttributeDefinitionPtr(new Assets::FloatAttributeDefinition(name, shortDescription, longDescription, defaultValue.value)); 468 return Assets::AttributeDefinitionPtr(new Assets::FloatAttributeDefinition(name, shortDescription, longDescription)); 469 } 470 parseChoicesAttribute(ParserStatus & status,const String & name)471 Assets::AttributeDefinitionPtr FgdParser::parseChoicesAttribute(ParserStatus& status, const String& name) { 472 const String shortDescription = parseAttributeDescription(status); 473 const DefaultValue<int> defaultValue = parseDefaultIntegerValue(status); 474 const String longDescription = parseAttributeDescription(status); 475 476 Token token; 477 expect(status, FgdToken::Equality, token = m_tokenizer.nextToken()); 478 expect(status, FgdToken::OBracket, token = m_tokenizer.nextToken()); 479 expect(status, FgdToken::Integer | FgdToken::Decimal | FgdToken::String | FgdToken::CBracket, token = m_tokenizer.nextToken()); 480 481 Assets::ChoiceAttributeOption::List options; 482 while (token.type() != FgdToken::CBracket) { 483 const String value = token.data(); 484 expect(status, FgdToken::Colon, token = m_tokenizer.nextToken()); 485 expect(status, FgdToken::String, token = m_tokenizer.nextToken()); 486 const String caption = token.data(); 487 options.push_back(Assets::ChoiceAttributeOption(value, caption)); 488 expect(status, FgdToken::Integer | FgdToken::Decimal | FgdToken::String | FgdToken::CBracket, token = m_tokenizer.nextToken()); 489 } 490 491 if (defaultValue.present) 492 return Assets::AttributeDefinitionPtr(new Assets::ChoiceAttributeDefinition(name, shortDescription, longDescription, options, static_cast<size_t>(defaultValue.value))); 493 return Assets::AttributeDefinitionPtr(new Assets::ChoiceAttributeDefinition(name, shortDescription, longDescription, options)); 494 } 495 parseFlagsAttribute(ParserStatus & status,const String & name)496 Assets::AttributeDefinitionPtr FgdParser::parseFlagsAttribute(ParserStatus& status, const String& name) { 497 // Flag attributes do not have descriptions or defaults, see https://developer.valvesoftware.com/wiki/FGD 498 499 Token token; 500 expect(status, FgdToken::Equality, token = m_tokenizer.nextToken()); 501 expect(status, FgdToken::OBracket, token = m_tokenizer.nextToken()); 502 expect(status, FgdToken::Integer | FgdToken::CBracket, token = m_tokenizer.nextToken()); 503 504 Assets::FlagsAttributeDefinition* definition = new Assets::FlagsAttributeDefinition(name); 505 506 while (token.type() != FgdToken::CBracket) { 507 const int value = token.toInteger<int>(); 508 expect(status, FgdToken::Colon, token = m_tokenizer.nextToken()); 509 510 expect(status, FgdToken::String, token = m_tokenizer.nextToken()); 511 const String shortDescription = token.data(); 512 513 bool defaultValue = false; 514 expect(status, FgdToken::Colon | FgdToken::Integer | FgdToken::CBracket, token = m_tokenizer.nextToken()); 515 if (token.type() == FgdToken::Colon) { 516 expect(status, FgdToken::Integer, token = m_tokenizer.nextToken()); 517 defaultValue = token.toInteger<int>() != 0; 518 } else { 519 m_tokenizer.pushToken(token); 520 } 521 522 expect(status, FgdToken::Integer | FgdToken::CBracket | FgdToken::Colon, token = m_tokenizer.nextToken()); 523 524 String longDescription; 525 if (token.type() == FgdToken::Colon) { 526 expect(status, FgdToken::String, token = m_tokenizer.nextToken()); 527 longDescription = token.data(); 528 expect(status, FgdToken::Integer | FgdToken::CBracket, token = m_tokenizer.nextToken()); 529 } 530 531 definition->addOption(value, shortDescription, longDescription, defaultValue); 532 } 533 534 return Assets::AttributeDefinitionPtr(definition); 535 } 536 parseUnknownAttribute(ParserStatus & status,const String & name)537 Assets::AttributeDefinitionPtr FgdParser::parseUnknownAttribute(ParserStatus& status, const String& name) { 538 const String shortDescription = parseAttributeDescription(status); 539 const DefaultValue<String> defaultValue = parseDefaultStringValue(status); 540 const String longDescription = parseAttributeDescription(status); 541 542 if (defaultValue.present) 543 return Assets::AttributeDefinitionPtr(new Assets::UnknownAttributeDefinition(name, shortDescription, longDescription, defaultValue.value)); 544 return Assets::AttributeDefinitionPtr(new Assets::UnknownAttributeDefinition(name, shortDescription, longDescription)); 545 } 546 parseAttributeDescription(ParserStatus & status)547 String FgdParser::parseAttributeDescription(ParserStatus& status) { 548 Token token = m_tokenizer.nextToken(); 549 if (token.type() == FgdToken::Colon) { 550 expect(status, FgdToken::String | FgdToken::Colon, token = m_tokenizer.nextToken()); 551 if (token.type() == FgdToken::String) 552 return token.data(); 553 } 554 m_tokenizer.pushToken(token); 555 return EmptyString; 556 } 557 parseDefaultStringValue(ParserStatus & status)558 FgdParser::DefaultValue<String> FgdParser::parseDefaultStringValue(ParserStatus& status) { 559 Token token = m_tokenizer.nextToken(); 560 if (token.type() == FgdToken::Colon) { 561 expect(status, FgdToken::String | FgdToken::Colon, token = m_tokenizer.nextToken()); 562 if (token.type() == FgdToken::String) 563 return DefaultValue<String>(token.data()); 564 } 565 566 m_tokenizer.pushToken(token); 567 return DefaultValue<String>(); 568 } 569 parseDefaultIntegerValue(ParserStatus & status)570 FgdParser::DefaultValue<int> FgdParser::parseDefaultIntegerValue(ParserStatus& status) { 571 Token token = m_tokenizer.nextToken(); 572 if (token.type() == FgdToken::Colon) { 573 expect(status, FgdToken::Integer | FgdToken::Decimal | FgdToken::Colon, token = m_tokenizer.nextToken()); 574 if (token.type() == FgdToken::Integer) 575 return DefaultValue<int>(token.toInteger<int>()); 576 else if (token.type() == FgdToken::Decimal) { // be graceful for DaZ 577 status.warn(token.line(), token.column(), "Found float default value for integer property"); 578 return DefaultValue<int>(static_cast<int>(token.toFloat<float>())); 579 } 580 } 581 582 m_tokenizer.pushToken(token); 583 return DefaultValue<int>(); 584 } 585 parseDefaultFloatValue(ParserStatus & status)586 FgdParser::DefaultValue<float> FgdParser::parseDefaultFloatValue(ParserStatus& status) { 587 Token token = m_tokenizer.nextToken(); 588 if (token.type() == FgdToken::Colon) { 589 // the default value should have quotes around it, but sometimes they're missing 590 expect(status, FgdToken::String | FgdToken::Decimal | FgdToken::Integer | FgdToken::Colon, token = m_tokenizer.nextToken()); 591 if (token.type() != FgdToken::Colon) { 592 if (token.type() != FgdToken::String) 593 status.warn(token.line(), token.column(), "Unquoted float default value " + token.data()); 594 return DefaultValue<float>(token.toFloat<float>()); 595 } 596 } 597 598 m_tokenizer.pushToken(token); 599 return DefaultValue<float>(); 600 } 601 parseVector(ParserStatus & status)602 Vec3 FgdParser::parseVector(ParserStatus& status) { 603 Token token; 604 Vec3 vec; 605 for (size_t i = 0; i < 3; i++) { 606 expect(status, FgdToken::Integer | FgdToken::Decimal, token = m_tokenizer.nextToken()); 607 vec[i] = token.toFloat<double>(); 608 } 609 return vec; 610 } 611 parseSize(ParserStatus & status)612 BBox3 FgdParser::parseSize(ParserStatus& status) { 613 Token token; 614 BBox3 size; 615 expect(status, FgdToken::OParenthesis, token = m_tokenizer.nextToken()); 616 size.min = parseVector(status); 617 expect(status, FgdToken::CParenthesis | FgdToken::Comma, token = m_tokenizer.nextToken()); 618 if (token.type() == FgdToken::Comma) { 619 size.max = parseVector(status); 620 expect(status, FgdToken::CParenthesis, token = m_tokenizer.nextToken()); 621 } else { 622 const Vec3 halfSize = size.min / 2.0; 623 size.min = -halfSize; 624 size.max = halfSize; 625 } 626 return size; 627 } 628 parseColor(ParserStatus & status)629 Color FgdParser::parseColor(ParserStatus& status) { 630 Color color; 631 Token token; 632 expect(status, FgdToken::OParenthesis, token = m_tokenizer.nextToken()); 633 for (size_t i = 0; i < 3; i++) { 634 expect(status, FgdToken::Decimal | FgdToken::Integer, token = m_tokenizer.nextToken()); 635 color[i] = token.toFloat<float>(); 636 if (color[i] > 1.0f) 637 color[i] /= 255.0f; 638 } 639 expect(status, FgdToken::CParenthesis, token = m_tokenizer.nextToken()); 640 color[3] = 1.0f; 641 return color; 642 } 643 } 644 } 645