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