Index: ps/trunk/source/ps/Parser.h =================================================================== --- ps/trunk/source/ps/Parser.h (revision 29) +++ ps/trunk/source/ps/Parser.h (revision 30) @@ -1,217 +1,217 @@ /* Customizeable Text Parser by Gee Gee@pyro.nu --Overview-- CParserValue Data! (an int, real, string etc), basically an argument CParserTaskType Syntax description for a line (ex. "variable=value") CParserLine Parse _one_ line CParser Include all syntax (CParserTaskTypes) The whole CParser* class group is used to read in config files and give instruction on how that should be made. The CParserTaskType declares what in a line is arguments, of course different CParserTaskTypes will exist, and it's up to the system to figure out which one acquired. --More Info-- - TODO: Write URL of documentation + http://forums.wildfiregames.com/0ad/index.php?showtopic=134 */ #ifndef __PARSER_H #define __PARSER_H #include "Prometheus.h" #pragma warning(disable:4786) //-------------------------------------------------------- // Includes / Compiler directives //-------------------------------------------------------- #include #include #include #include #include //------------------------------------------------- // Types //------------------------------------------------- enum _ParserValueType { typeIdent, typeValue, typeRest, typeAddArg }; //------------------------------------------------- // Declarations //------------------------------------------------- class CParserValue; class CParserTaskType; class CParserLine; class CParser; // CParserValue // --------------------------------------------------------------------- // A parser value represents an argument // color=r, g, b // r, g and b will be CParserValues, or the arguments as they are called. // This class can store only a string, but can try parsing it to different // types class CParserValue { public: CParserValue(); ~CParserValue(); // return is error status _bool GetString(std::string &ret); _bool GetBool(_bool &ret); _bool GetChar(_char &ret); // As number! otherwise use GetString make sure size=1 _bool GetShort(_short &ret); _bool GetInt(_int &ret); _bool GetLong(_long &ret); _bool GetUnsignedShort(_ushort &ret); _bool GetUnsignedInt(_uint &ret); _bool GetUnsignedLong(_ulong &ret); _bool GetFloat(float &ret); _bool GetDouble(double &ret); // Memory regardless if it's an int, real, string or whatever std::string m_String; }; // CParserTaskTypeNode // ---------------------------------------------------------------------| Class // A task type is basically a tree, this is because dynamic arguments // requires alternative routes, so basically it's a binary tree with an // obligatory next node (if it's not the end of the tree) and an alternative // dynamic arguments node // // If we are at the beginning of this string, this will be the layout of the node // "<$value_>:_" // // m_Element ":" // m_AltNode => "$value" // m_NextNode => "_" // class CParserTaskTypeNode { public: CParserTaskTypeNode(); ~CParserTaskTypeNode(); // Free node pointers that are below this void DeleteChildren(); // Either the node is a letter or a type, if m_Leter is '\0' // then check m_Type what it is _char m_Letter; _ParserValueType m_Type; std::string m_String; // Used for diverse storage // mainly for the typeAddArg // Parent node CParserTaskTypeNode *m_ParentNode; // Next node CParserTaskTypeNode *m_NextNode; // true means AltNode can be looped <...> // false means AltNode is just an optional part [...] _bool m_AltNodeRepeatable; // Whenever a dynamic argument is used, it's first node is stored in this // as an alternative node. The parser first checks if there is an // alternative route, and if it applies to the next node. If not, proceed // as usual with m_String and the next node CParserTaskTypeNode *m_AltNode; // There are different kinds of alternative routes //int m_AltRouteType; }; // CParserTaskType // ---------------------------------------------------------------------| Class // A task type is basically different kinds of lines for the parser // variable=value is one type... class CParserTaskType { public: CParserTaskType(); ~CParserTaskType(); // Delete the whole tree void DeleteTree(); CParserTaskTypeNode *m_BaseNode; // Something to identify it with std::string m_Name; }; // CParserLine // ---------------------------------------------------------------------| Class // Representing one line, i.e. one task, in a config file class CParserLine { public: CParserLine(); ~CParserLine(); std::deque m_Arguments; _bool m_ParseOK; // same as ParseString will return std::string m_TaskTypeName; // Name of the task type found protected: _bool ClearArguments(); public: // Interface _bool ParseString(const CParser& parser, std::string line); // Methods for getting arguments // it returns success _bool GetArgString (const _int& arg, std::string &ret); _bool GetArgBool (const _int& arg, _bool &ret); _bool GetArgChar (const _int& arg, _char &ret); _bool GetArgShort (const _int& arg, _short &ret); _bool GetArgInt (const _int& arg, _int &ret); _bool GetArgLong (const _int& arg, _long &ret); _bool GetArgUnsignedShort (const _int& arg, _ushort &ret); _bool GetArgUnsignedInt (const _int& arg, _uint &ret); _bool GetArgUnsignedLong (const _int& arg, _ulong &ret); _bool GetArgFloat (const _int& arg, float &ret); _bool GetArgDouble (const _int& arg, double &ret); // Get Argument count _int GetArgCount() const { return m_Arguments.size(); } }; // CParser // ---------------------------------------------------------------------| Class // Includes parsing instruction, i.e. task-types class CParser { public: CParser::CParser(); CParser::~CParser(); std::vector m_TaskTypes; // Interface _bool InputTaskType(const std::string& strName, const std::string& strSyntax); }; #endif Index: ps/trunk/source/ps/Parser.cpp =================================================================== --- ps/trunk/source/ps/Parser.cpp (revision 29) +++ ps/trunk/source/ps/Parser.cpp (revision 30) @@ -1,1015 +1,1008 @@ // last modified Thursday, May 08, 2003 -#include "parser.h" +#include "Parser.h" #pragma warning(disable:4786) using namespace std; //------------------------------------------------- // Macros //------------------------------------------------- -#if 1 -# define for if(false); else for -#endif // MSVC - #define REGULAR_MAX_LENGTH 10 #define START_DYNAMIC '<' #define END_DYNAMIC '>' #define START_OPTIONAL '[' #define END_OPTIONAL ']' #define REGULAR_EXPRESSION '$' // use GetDouble and type-cast it to <> #define FUNC_IMPL_CAST_GETDOUBLE(func_name,type) \ _bool CParserValue::func_name(type &ret) \ { \ _double d; \ if (GetDouble(d)) \ return ret = (type)d, true; \ else \ return false; \ } // Function-implementation creator for GetArg%type% that will call // Get%type% from the CParserValue // func_name must belong to CParserFile #define FUNC_IMPL_GETARG(func_name, get_name, type) \ _bool CParserLine::func_name(const _int & arg, type &ret) \ { \ if (GetArgCount() <= arg) \ return false; \ return m_Arguments[arg].get_name(ret); \ } //------------------------------------------------- // Function definitions //------------------------------------------------- static _bool _IsStrictNameChar(const _char& c); static _bool _IsValueChar(const _char& c); // Functions used for checking a character if it belongs to a value // or not // Checks ident static _bool _IsStrictNameChar(const _char& c) { return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')); } // Checks value static _bool _IsValueChar(const _char& c) { return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c=='.' || c=='_'); } // CParserValue // ---------------------------------------------------------------------| Class CParserValue::CParserValue() { } CParserValue::~CParserValue() { } // Parse the string in Value to different types // bool _bool CParserValue::GetBool(_bool &ret) { // TODO Raj Add or remove some? I can make it all lowercase // first so True and TRUE also works, or you could just // add them too // true if (m_String == "true" || m_String == "on" || m_String == "1" || m_String == "yes") { ret = true; return true; } else // false if (m_String == "false" || m_String == "off" || m_String == "0" || m_String == "no") { ret = false; return true; } // point only erroneous runs reach return false; } // double _bool CParserValue::GetDouble(_double &ret) { // locals _double TempRet = 0.0; _int Size = m_String.size(); _int i; _bool AtLeastOne = false; // Checked if at least one of the loops // run, otherwise "." would parse OK _int DecimalPos; _bool Negative = false; // "-" is found // Check if '-' is found if (m_String[0]=='-') { Negative = true; } // find decimal position DecimalPos = m_String.find("."); if (DecimalPos == string::npos) DecimalPos = Size; // Iterate left of the decimal sign // for (i=(Negative?1:0); i < DecimalPos; ++i) { // Set AtLeastOne to true AtLeastOne = true; // Check if a digit is found if (m_String[i] >= '0' && m_String[i] <= '9') { TempRet += (m_String[i]-'0')*pow(10,(DecimalPos-i-1)); } else { // parse error! return false; } } // Iterate right of the decimal sign // for (i=DecimalPos+1; i < Size; ++i) { // Set AtLeastOne to true AtLeastOne = true; // Check if a digit is found if (m_String[i] >= '0' && m_String[i] <= '9') { TempRet += (m_String[i]-'0')*pow(10,DecimalPos-i); } // It will accept and ending f, like 1.0f else if (!(i==Size-1 && m_String[i] == 'f')) { // parse error! return false; } } if (!AtLeastOne)return false; // Set the reference to the temp value and return success ret = (Negative?-TempRet:TempRet); return true; } // string - only return m_String, can't fail _bool CParserValue::GetString(std::string &ret) { ret = m_String; return true; } // These macros include the IMPLEMENTATION of the // the function in the macro argument for CParserValue // They use GetDouble, and then type-cast it FUNC_IMPL_CAST_GETDOUBLE(GetFloat, _float) FUNC_IMPL_CAST_GETDOUBLE(GetChar, _char) FUNC_IMPL_CAST_GETDOUBLE(GetShort, _short) FUNC_IMPL_CAST_GETDOUBLE(GetInt, _int) FUNC_IMPL_CAST_GETDOUBLE(GetLong, _long) FUNC_IMPL_CAST_GETDOUBLE(GetUnsignedShort, _ushort) FUNC_IMPL_CAST_GETDOUBLE(GetUnsignedInt, _uint) FUNC_IMPL_CAST_GETDOUBLE(GetUnsignedLong, _ulong) // CParserTaskTypeNode // ---------------------------------------------------------------------| Class CParserTaskTypeNode::CParserTaskTypeNode() : m_ParentNode(NULL), m_NextNode(NULL), m_AltNode(NULL) { } CParserTaskTypeNode::~CParserTaskTypeNode() { } // Delete all children void CParserTaskTypeNode::DeleteChildren() { // Delete nodes if applicable if (m_NextNode) { m_NextNode->DeleteChildren(); delete m_NextNode; m_NextNode = NULL; } if (m_AltNode) { m_AltNode->DeleteChildren(); delete m_AltNode; m_AltNode = NULL; } } // CParserTaskType // ---------------------------------------------------------------------| Class CParserTaskType::CParserTaskType() : m_BaseNode(NULL) { } CParserTaskType::~CParserTaskType() { } // Delete m_BaseNode and all of its children void CParserTaskType::DeleteTree() { if (m_BaseNode) { m_BaseNode->DeleteChildren(); delete m_BaseNode; m_BaseNode = NULL; } } // CParserLine // ---------------------------------------------------------------------| Class CParserLine::CParserLine() { } CParserLine::~CParserLine() { ClearArguments(); } // Clear arguments (deleting m_Memory _bool CParserLine::ClearArguments() { // Now we can actually clear it m_Arguments.clear(); return true; } // Implementation of CParserFile::GetArg* // it just checks if argument isn't out of range, and // then it uses the the respective function in CParserValue FUNC_IMPL_GETARG(GetArgString, GetString, string) FUNC_IMPL_GETARG(GetArgBool, GetBool, _bool) FUNC_IMPL_GETARG(GetArgChar, GetChar, _char) FUNC_IMPL_GETARG(GetArgShort, GetShort, _short) FUNC_IMPL_GETARG(GetArgInt, GetInt, _int) FUNC_IMPL_GETARG(GetArgLong, GetLong, _long) FUNC_IMPL_GETARG(GetArgUnsignedShort, GetUnsignedShort, _ushort) FUNC_IMPL_GETARG(GetArgUnsignedInt, GetUnsignedInt, _uint) FUNC_IMPL_GETARG(GetArgUnsignedLong, GetUnsignedLong, _ulong) FUNC_IMPL_GETARG(GetArgFloat, GetFloat, _float) FUNC_IMPL_GETARG(GetArgDouble, GetDouble, _double) // ParseString // ------------------------------------------------------------------| Function // Parses a line, dividing it into segments according to defined semantics // each segment is called an argument and represents a value of some kind // ex: // variable = 5 => variable, =, 5 // CallFunc(4,2) => CallFunc, 4, 2 _bool CParserLine::ParseString(const CParser& Parser, string strLine) { // Don't process empty string if (strLine == string()) { m_ParseOK = false; // Empty lines should never be inputted by CParserFile return m_ParseOK; } // Locals _bool Extract=false; _int ExtractPos=0; _char Buffer[256]; _char Letter[] = {'\0','\0'}; // Letter as string vector Segments; string strSub; // Set result to false, then if a match is found, turn it true m_ParseOK = false; /* TODO Gee Remove this comment! // Remove C++-styled comments! // * * * * int pos = strLine.find("//"); if (pos != string::npos) strLine = strLine.substr(0,pos); */ // Divide string into smaller vectors, seperators are unusual signs // * * * * for (_int i=0; i= 256) { Extract=false; } else { // Extract string after $ ! // break whenever we reach a sign that's not A-Z a-z if (_IsValueChar(strLine[i])) { Buffer[i-ExtractPos] = strLine[i]; } else { // Extraction is finished Extract=false; // strLine[i] is now a non-regular character // we'll jump back one step so that will // be included next loop --i; } // Check if string is complete if (i == strLine.size()-1) Extract=false; } // If extraction was finished! Input Buffer if (Extract == false) { Segments.push_back( string(Buffer) ); } } } // Try to find an appropriate CParserTaskType in parser // * * * * // Locals _int Progress; // progress in Segments index _int Lane=0; // Have many alternative routes we are in _bool Match; // If a task-type match has been found // The vector of these three represents the different lanes // LastValidProgress[1] takes you back to lane 1 and how // the variables was set at that point vector<_int> LastValidProgress; // When diving into a dynamic argument store store // the last valid so you can go back to it vector<_int> LastValidArgCount; // If an alternative route turns out to fail, we // need to know the amount of arguments on the last // valid position, so we can remove them. vector<_bool> LastValidMatch; // Match at that point _bool BlockAltNode = false; // If this turns true, the alternative route // tested was not a success, and the settings // should be set back in order to test the // next node instead _bool LookNoFurther = false; // If this turns true, it means a definite match has been // found and no further looking is required CParserTaskTypeNode *CurNode=NULL; // Current node on task type CParserTaskTypeNode *PrevNode=NULL; // Last node // Iterate all different TaskType, and all TaskTypeElements... // start from left and go to the right (prog), comparing // the similarities. If enough // similarities are found, then we can declare progress as // that type and exit loop vector::const_iterator cit_tt; for (cit_tt = Parser.m_TaskTypes.begin(); cit_tt != Parser.m_TaskTypes.end(); ++cit_tt) { // Reset for this task-type Match = true; Progress = 0; ClearArguments(); // Previous failed can have filled this CurNode = cit_tt->m_BaseNode; // Start at base node LookNoFurther = false; BlockAltNode = false; // This loop will go through the whole tree until // it reaches an empty node while (!LookNoFurther) { // Check if node is valid // otherwise try to jump back to parent if (CurNode->m_NextNode == NULL && (CurNode->m_AltNode == NULL || BlockAltNode)) { // Jump back to valid //CurNode = PrevNode; // If the node has no children, it's the last, and we're // on lane 0, i.e. with no if (CurNode->m_NextNode == NULL && (CurNode->m_AltNode == NULL || BlockAltNode) && Lane == 0) { if (Progress != Segments.size()) Match = false; break; } else { CParserTaskTypeNode *OldNode = NULL; // Go back to regular route! while (1) { OldNode = CurNode; CurNode = CurNode->m_ParentNode; if (CurNode->m_AltNode == OldNode) { break; } } // If the alternative route isn't repeatable, block alternative route for // next loop cycle if (!CurNode->m_AltNodeRepeatable) BlockAltNode = true; // Decrease lane --Lane; } } // Check alternative route // * * * * // Check if alternative route is present // note, if an alternative node has already failed // we don't want to force usage of the next node // therefore BlockAltNode has to be false if (!BlockAltNode) { if (CurNode->m_AltNode) { // Alternative route found, we'll test this first! CurNode = CurNode->m_AltNode; // --- New node is set! // Make sure they are large enough if (LastValidProgress.size() < Lane+1) { LastValidProgress.resize(Lane+1); LastValidMatch.resize(Lane+1); LastValidArgCount.resize(Lane+1); } // Store last valid progress LastValidProgress[Lane] = Progress; LastValidMatch[Lane] = Match; LastValidArgCount[Lane] = m_Arguments.size(); ++Lane; continue; } } else BlockAltNode = false; // Now check Regular Next Node // * * * * if (CurNode->m_NextNode) { // Important! // Change working node to the next node! CurNode = CurNode->m_NextNode; // --- New node is set! // CHECK IF LETTER IS CORRECT if (CurNode->m_Letter != '\0') { // OPTIONALLY SKIP BLANK SPACES if (CurNode->m_Letter == '_') { // Find blank space if any! // and jump to the next non-blankspace if (Progress < Segments.size()) { // Skip blankspaces AND tabs! while (Segments[Progress].size()==1 && (Segments[Progress][0]==' ' || Segments[Progress][0]=='\t')) { ++Progress; // Check length if (Progress >= Segments.size()) { break; } } } } else // CHECK LETTER IF IT'S CORRECT { if (Progress < Segments.size()) { // This should be 1-Letter long if (Segments[Progress].size() != 1) Match = false; // Check Letter if (CurNode->m_Letter != Segments[Progress][0]) Match = false; // Update progress ++Progress; } else Match = false; } } // CHECK NAME else { // Do this first, because we wan't to // avoid the Progress and Segments.size() // check for this if (CurNode->m_Type == typeAddArg) { // Input argument CParserValue value; value.m_String = CurNode->m_String; m_Arguments.push_back(value); } else { // Alright! An ident or const has been acquired, if we // can't find any or if the string has run out // that invalidates the match // String end? if (Progress >= Segments.size()) { Match = false; } else { // Store argument in CParserValue! CParserValue value; _int i; switch(CurNode->m_Type) { case typeIdent: // Check if this really is a string if (!_IsStrictNameChar(Segments[Progress][0])) { Match = false; break; } // Same as at typeValue, but this time // we won't allow strings like "this", just // like this if (Segments[Progress][0] == '\"') Match = false; else value.m_String = Segments[Progress]; // Input argument! m_Arguments.push_back(value); ++Progress; break; case typeValue: // Check if this really is a string if (!_IsValueChar(Segments[Progress][0]) && Segments[Progress][0] != '\"') { Match = false; break; } // Check if initial is -> " <-, because that means it was // stored from a "String like these with quotes" // We don't want to store that prefix if (Segments[Progress][0] == '\"') value.m_String = Segments[Progress].substr(1, Segments[Progress].size()-1); else value.m_String = Segments[Progress]; // Input argument! m_Arguments.push_back(value); ++Progress; break; case typeRest: // Extract the whole of the string // Reset, probably is but still value.m_String = string(); for (i=Progress; i " <=, add one to the end of it too if (Segments[i][0] == '"') value.m_String += "\""; } m_Arguments.push_back(value); // Now BREAK EVERYTHING ! // We're done, we found are match and let's get out LookNoFurther = true; //Match = true; break; default: break; } } } } } // Check if match is false! if it is, try returning to last valid state if (!Match && Lane > 0) { // The alternative route failed BlockAltNode = true; CParserTaskTypeNode *OldNode = NULL; // Go back to regular route! while (1) { OldNode = CurNode; CurNode = CurNode->m_ParentNode; if (CurNode->m_AltNode == OldNode) { break; } } // Decrease lane --Lane; // Restore values as before Progress = LastValidProgress[Lane]; Match = LastValidMatch[Lane]; m_Arguments.resize(LastValidArgCount[Lane]); } } // Check if it was a match! if (Match) { // Before we celebrate the match, let's check if whole // of Segments has been used, and if so we have to // nullify the match //if (Progress == Segments.size()) { // *** REPORT MATCH WAS FOUND *** m_TaskTypeName = cit_tt->m_Name; m_ParseOK = true; break; } } } // POST-PROCESSING OF ARGUMENTS! // if _minus is found as argument, remove it and add "-" to the one after that // note, it's easier if std::iterator isn't used here for (_int i=1; i::iterator itTT; for (itTT = m_TaskTypes.begin(); itTT != m_TaskTypes.end(); ++itTT) { itTT->DeleteTree(); } } // InputTaskType // ------------------------------------------------------------------| Function // A task-type is a string representing the acquired syntax when parsing // This function converts that string into a binary tree, making it easier // and faster to later parse. _bool CParser::InputTaskType(const string& strName, const string& strSyntax) { // Locals CParserTaskType TaskType; // Object we acquire to create _char Buffer[REGULAR_MAX_LENGTH]; _int ExtractPos = 0; _bool Extract = false; _bool Error = false; _bool ConstructNew = false; // If it's the first input, then don't // construct a new node, because we // we already have m_BaseNode // Construct base node TaskType.m_BaseNode = new CParserTaskTypeNode(); // Working node CParserTaskTypeNode *CurNode = TaskType.m_BaseNode; // Loop through the string and construct nodes in the binary tree // when applicable for (_int i=0; im_AltNode = new CParserTaskTypeNode(); CurNode->m_AltNode->m_ParentNode = CurNode; // It's repeatable CurNode->m_AltNodeRepeatable = _bool(strSyntax[i]==START_DYNAMIC); // Set to current CurNode = CurNode->m_AltNode; ConstructNew = false; // We're done extracting for now continue; } else if (strSyntax[i] == END_DYNAMIC || strSyntax[i] == END_OPTIONAL) { CParserTaskTypeNode *OldNode = NULL; // Jump out of this alternative route while (1) { OldNode = CurNode; CurNode = CurNode->m_ParentNode; if (CurNode == NULL) { // Syntax error Error = true; break; } if (CurNode->m_AltNode == OldNode) { break; } } if (Error)break; } else { // Check if this is the first input // CONSTRUCT A CHILD NODE CurNode->m_NextNode = new CParserTaskTypeNode(); CurNode->m_NextNode->m_ParentNode = CurNode; // Jump into ! CurNode = CurNode->m_NextNode; // Set CurNode CurNode->m_Letter = strSyntax[i]; } } // Extact if (Extract) { // No type names are longer than REGULAR_MAX_LENGTH characters if (i-ExtractPos >= REGULAR_MAX_LENGTH) { Extract=false; } else { // Extract string after $ ! // break whenever we reach a sign that's not A-Z a-z if (_IsStrictNameChar(strSyntax[i])) { Buffer[i-ExtractPos] = strSyntax[i]; } else { // Extraction is finished Extract=false; // strLine[i] is now a non-regular character // we'll jump back one step so that will // be included next loop --i; } // Check if string is complete if (i == strSyntax.size()-1) Extract=false; } // If extraction was finished! Input Buffer if (Extract == false) { // CONSTRUCT A CHILD NODE CurNode->m_NextNode = new CParserTaskTypeNode(); CurNode->m_NextNode->m_ParentNode = CurNode; // Jump into ! CurNode = CurNode->m_NextNode; CurNode->m_Letter = '\0'; string str = string(Buffer); // Check value and set up CurNode accordingly if (str == "value") CurNode->m_Type = typeValue; else if (str == "ident") CurNode->m_Type = typeIdent; else if (str == "rest") CurNode->m_Type = typeRest; else if (str == "rbracket") CurNode->m_Letter = '>'; else if (str == "lbracket") CurNode->m_Letter = '<'; else if (str == "rbrace") CurNode->m_Letter = ']'; else if (str == "lbrace") CurNode->m_Letter = '['; else if (str == "dollar") CurNode->m_Letter = '$'; else if (str == "arg") { // After $arg, you need a parenthesis, within that parenthesis is a string // that will be added as an argument when it's passed through CurNode->m_Type = typeAddArg; // Check length, it has to have place for at least a '(' and ')' after $arg if (ExtractPos+4 >= strSyntax.size()) { Error = true; break; } // We want to extract what's inside the parenthesis after $arg // if it's not there at all, it's a syntactical error if (strSyntax[ExtractPos+3] != '(') { Error = true; break; } // Now try finding the second ')' _int Pos = strSyntax.find(")", ExtractPos+5); // Check if ')' exists at all if (Pos == string::npos) { Error = true; break; } // Now extract string within ( and ) CurNode->m_String = strSyntax.substr(ExtractPos+4, Pos-(ExtractPos+4)); // Now update position i = Pos; } else { // TODO Gee report in log too Error = true; } } } } // Input TaskType if (!Error) { // Set name and input TaskType.m_Name = strName; m_TaskTypes.push_back(TaskType); } return !Error; -} - -/* End of PARSER.CPP -******************************************************************************/ +} \ No newline at end of file