/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Additional permissions under GNU GPL version 3 section 7 If you modify this Program, or any covered work, by linking or combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK (or a modified version of those libraries), containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the licensors of this Program grant you additional permission to convey the resulting work. Corresponding Source for a non-source form of such a combination shall include the source code for the parts of OpenSSL and IJG JPEG Library used as well as that of the covered work. You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ /***************************************************************************** * * $/Plasma20/Sources/Plasma/NucleusLib/pnUtils/Private/pnUtCmd.cpp * ***/ #include "../Pch.h" #pragma hdrstop /***************************************************************************** * * Private * ***/ #define WHITESPACE L" \"\t\r\n\x1A" #define FLAGS L"-/" #define SEPARATORS L"=:" #define TOGGLES L"+-" #define ALL WHITESPACE FLAGS SEPARATORS TOGGLES static const unsigned kMaxTokenLength = MAX_PATH; struct CmdArgData { CmdArgDef def; union { bool boolVal; float floatVal; int intVal; const wchar * strVal; unsigned unsignedVal; } val; wchar * buffer; unsigned nameChars; bool isSpecified; ~CmdArgData () { if (buffer) FREE(buffer); } }; struct CmdTokState { CCmdParser * parser; unsigned pendingIndex; unsigned unflaggedIndex; }; class CICmdParser { private: FARRAYOBJ(CmdArgData) m_argArray; FARRAY(unsigned) m_idLookupArray; unsigned m_requiredCount; FARRAY(unsigned) m_unflaggedArray; inline bool CheckFlag (unsigned flags, unsigned flag, unsigned mask) const; void Error (const CmdTokState * state, ECmdError errorCode, const wchar arg[], const wchar value[]) const; bool LookupFlagged (const wchar ** name, unsigned * lastIndex) const; bool ProcessValue (CmdTokState * state, unsigned index, const wchar str[]); void SetDefaultValue (CmdArgData & arg); bool TokenizeFlags (CmdTokState * state, const wchar str[]); public: CICmdParser (const CmdArgDef def[], unsigned defCount); bool CheckAllRequiredArguments (CmdTokState * state); const CmdArgData * FindArgById (unsigned id) const; const CmdArgData * FindArgByName (const wchar name[]) const; bool Tokenize (CmdTokState * state, const wchar str[]); }; /***************************************************************************** * * CICmdParser implementation * ***/ //=========================================================================== bool CICmdParser::CheckFlag (unsigned flags, unsigned flag, unsigned mask) const { return ((flags & mask) == flag); } //=========================================================================== bool CICmdParser::CheckAllRequiredArguments (CmdTokState * state) { bool result = (state->unflaggedIndex >= m_requiredCount); if (!result) Error(state, kCmdErrorTooFewArgs, nil, nil); return result; } //=========================================================================== CICmdParser::CICmdParser (const CmdArgDef def[], unsigned defCount) { unsigned loop; // Save the argument definitions unsigned maxId = 0; unsigned unflaggedCount = 0; m_argArray.SetCount(defCount); for (loop = 0; loop < defCount; ++loop) { // Check whether this argument is flagged bool flagged = CheckFlag(def[loop].flags, kCmdArgFlagged, kCmdMaskArg); // Disallow names on unflagged arguments ASSERT(flagged || !def[loop].name); // Store the argument data CmdArgData & arg = m_argArray[loop]; arg.def = def[loop]; arg.buffer = nil; arg.nameChars = def[loop].name ? StrLen(def[loop].name) : 0; arg.isSpecified = false; SetDefaultValue(arg); maxId = max(maxId, def[loop].id); // Track the number of unflagged arguments if (!flagged) ++unflaggedCount; } // Build the id lookup table unsigned idTableSize = min(maxId + 1, defCount * 2); m_idLookupArray.SetCount(idTableSize); m_idLookupArray.Zero(); for (loop = 0; loop < defCount; ++loop) if (def[loop].id < idTableSize) m_idLookupArray[def[loop].id] = loop; // Build the unflagged array unsigned unflaggedIndex = 0; m_unflaggedArray.SetCount(unflaggedCount); for (loop = 0; loop < defCount; ++loop) if (CheckFlag(def[loop].flags, kCmdArgRequired, kCmdMaskArg)) m_unflaggedArray[unflaggedIndex++] = loop; m_requiredCount = unflaggedIndex; for (loop = 0; loop < defCount; ++loop) if (!(CheckFlag(def[loop].flags, kCmdArgFlagged, kCmdMaskArg) || CheckFlag(def[loop].flags, kCmdArgRequired, kCmdMaskArg))) m_unflaggedArray[unflaggedIndex++] = loop; } //=========================================================================== void CICmdParser::Error (const CmdTokState * state, ECmdError errorCode, const wchar arg[], const wchar value[]) const { // Compose the error text // (This text is only provided as a shortcut for trivial applications that // don't want to compose their own text. Normally, an application would // compose error text using its own localized strings.) unsigned chars = 256 + (arg ? StrLen(arg) : 0) + (value ? StrLen(value) : 0); wchar * buffer = (wchar *)ALLOC(chars * sizeof(wchar)); switch (errorCode) { case kCmdErrorInvalidArg: StrPrintf(buffer, chars, L"Invalid argument: %s", arg); break; case kCmdErrorInvalidValue: StrPrintf(buffer, chars, L"Argument %s invalid value: %s", arg, value); break; case kCmdErrorTooFewArgs: StrPrintf(buffer, chars, L"Too few arguments"); break; case kCmdErrorTooManyArgs: StrPrintf(buffer, chars, L"Too many arguments: %s", arg); break; DEFAULT_FATAL(errorCode); } // Call the error handler state->parser->OnError(buffer, errorCode, arg, value); // Free memory FREE(buffer); } //=========================================================================== const CmdArgData * CICmdParser::FindArgById (unsigned id) const { // Search for the argument with this id unsigned index; if (id < m_idLookupArray.Count()) index = m_idLookupArray[id]; else for (index = 0; index < m_argArray.Count(); ++index) if (m_argArray[index].def.id == id) break; // Verify that we found the correct argument if ( (index >= m_argArray.Count()) || (m_argArray[index].def.id != id) ) return nil; // Return the argument data return &m_argArray[index]; } //=========================================================================== const CmdArgData * CICmdParser::FindArgByName (const wchar name[]) const { // Search for an argument with this name unsigned index = (unsigned)-1; if (!LookupFlagged(&name, &index)) return nil; // Return the argument data return &m_argArray[index]; } //=========================================================================== bool CICmdParser::LookupFlagged (const wchar ** name, unsigned * lastIndex) const { unsigned argCount = m_argArray.Count(); unsigned chars = StrLen(*name); unsigned bestIndex = (unsigned)-1; unsigned bestChars = 0; // Check whether this argument is a suffix to any previously // provided prefix in this token for (unsigned prevChars = (*lastIndex != (unsigned)-1) ? m_argArray[*lastIndex].nameChars : 0; (prevChars != (unsigned)-1) && !bestChars; --prevChars) { const CmdArgData & prev = prevChars ? m_argArray[*lastIndex] : *(const CmdArgData *)nil; // Find this argument in the list for (unsigned index = 0; index < argCount; ++index) { const CmdArgData & arg = m_argArray[index]; // Ignore non-flagged arguments if (!CheckFlag(arg.def.flags, kCmdArgFlagged, kCmdMaskArg)) continue; // Ignore this argument if it wouldn't beat the previous best match if (arg.nameChars < bestChars + prevChars) continue; // Ignore this argument if it doesn't match the prefix bool caseSensitive = CheckFlag(arg.def.flags, kCmdCaseSensitive, kCmdCaseSensitive); if ( prevChars && ( (prevChars >= arg.nameChars) || ( caseSensitive && StrCmp(arg.def.name, prev.def.name, prevChars)) || (!caseSensitive && StrCmpI(arg.def.name, prev.def.name, prevChars)) ) ) continue; // Ignore this argument if it doesn't match the suffix if ( ( caseSensitive && StrCmp(*name, arg.def.name + prevChars, arg.nameChars - prevChars)) || (!caseSensitive && StrCmpI(*name, arg.def.name + prevChars, arg.nameChars - prevChars)) ) continue; // Track the best match bestIndex = index; bestChars = arg.nameChars - prevChars; if (bestChars == chars) break; } } // Return the result *name += bestChars; *lastIndex = bestIndex; return (bestChars != 0); } //=========================================================================== bool CICmdParser::ProcessValue (CmdTokState * state, unsigned index, const wchar str[]) { CmdArgData & arg = m_argArray[index]; arg.isSpecified = true; unsigned argType = arg.def.flags & kCmdMaskType; switch (argType) { case kCmdTypeBool: if (*str == '+') arg.val.boolVal = true; else if (*str == '-') arg.val.boolVal = false; else if (!*str) arg.val.boolVal = CheckFlag(arg.def.flags, kCmdBoolSet, kCmdMaskBool); else Error(state, kCmdErrorInvalidValue, arg.def.name, str); break; case kCmdTypeFloat: { const wchar * endPtr; arg.val.floatVal = StrToFloat(str, &endPtr); if (*endPtr) Error(state, kCmdErrorInvalidValue, arg.def.name, str); } break; case kCmdTypeInt: { const wchar * endPtr; arg.val.intVal = StrToInt(str, &endPtr); if (*endPtr) Error(state, kCmdErrorInvalidValue, arg.def.name, str); } break; case kCmdTypeString: if (arg.buffer) FREE(arg.buffer); arg.buffer = StrDup(str); arg.val.strVal = arg.buffer; break; case kCmdTypeUnsigned: { const wchar * endPtr; arg.val.unsignedVal = StrToUnsigned(str, &endPtr, 10); if (*endPtr) Error(state, kCmdErrorInvalidValue, arg.def.name, str); } break; DEFAULT_FATAL(argType); } return true; } //=========================================================================== void CICmdParser::SetDefaultValue (CmdArgData & arg) { unsigned argType = arg.def.flags & kCmdMaskType; switch (argType) { case kCmdTypeBool: arg.val.boolVal = !CheckFlag(arg.def.flags, kCmdBoolSet, kCmdMaskBool); break; case kCmdTypeInt: arg.val.intVal = 0; break; case kCmdTypeUnsigned: arg.val.unsignedVal = 0; break; case kCmdTypeFloat: arg.val.floatVal = 0.0f; break; case kCmdTypeString: arg.val.strVal = L""; break; DEFAULT_FATAL(argType); } } //=========================================================================== bool CICmdParser::Tokenize (CmdTokState * state, const wchar str[]) { wchar buffer[kMaxTokenLength]; bool result = true; while (result && StrTokenize(&str, buffer, arrsize(buffer), WHITESPACE)) { // If the previous argument is awaiting a value, then use this token // as the value if (state->pendingIndex != (unsigned)-1) { result = ProcessValue(state, state->pendingIndex, buffer); state->pendingIndex = (unsigned)-1; continue; } // Identify and process flagged parameters if (StrChr(FLAGS, buffer[0]) && TokenizeFlags(state, buffer)) continue; // Process unflagged parameters if (state->unflaggedIndex < m_unflaggedArray.Count()) { result = ProcessValue(state, m_unflaggedArray[state->unflaggedIndex++], buffer); continue; } // Process extra parameters if (state->parser->OnExtra(buffer)) continue; // Process invalid parameters Error(state, kCmdErrorTooManyArgs, buffer, nil); result = false; break; } return result; } //=========================================================================== bool CICmdParser::TokenizeFlags (CmdTokState * state, const wchar str[]) { // Process each separately flagged token within the string wchar buffer[kMaxTokenLength]; bool result = true; while (result && StrTokenize(&str, buffer, arrsize(buffer), ALL)) { if (!buffer[0]) continue; // Process each flag within the token unsigned lastIndex = (unsigned)-1; const wchar * bufferPtr = buffer; while (result) { // Lookup the argument name result = LookupFlagged(&bufferPtr, &lastIndex); if (!result) { Error(state, kCmdErrorInvalidArg, bufferPtr, nil); result = false; break; } // If this argument is boolean, allow it to share a common prefix // with the next argument. In this case there is no place for // the user to provide a value, so use the default value. if (*bufferPtr && CheckFlag(m_argArray[lastIndex].def.flags, kCmdTypeBool, kCmdMaskType)) { result = ProcessValue(state, lastIndex, L""); continue; } break; } if (!result) break; // Check for an argument value provided using a separator if (*str && StrChr(SEPARATORS, *str)) { result = ProcessValue(state, lastIndex, str + 1); break; } // Process values for boolean arguments if (CheckFlag(m_argArray[lastIndex].def.flags, kCmdTypeBool, kCmdMaskType)) { // Check for a value provided with a toggle if (*str && StrChr(TOGGLES, *str)) { wchar tempStr[] = {*str, 0}; result = ProcessValue(state, lastIndex, tempStr); ++str; continue; } // Check for a default value else { result = ProcessValue(state, lastIndex, L""); continue; } } // Process values for non-boolean arguments else { // Check for an argument value immediately following the name if (*bufferPtr) { result = ProcessValue(state, lastIndex, bufferPtr); break; } // Check for an argument value in the next token else { state->pendingIndex = lastIndex; break; } } } return result; } /***************************************************************************** * * CCmdParser implementation * ***/ //=========================================================================== CCmdParser::CCmdParser () { fParser = nil; } //=========================================================================== CCmdParser::CCmdParser (const CmdArgDef def[], unsigned defCount) { Initialize(def, defCount); } //=========================================================================== CCmdParser::~CCmdParser () { DEL(fParser); } //=========================================================================== bool CCmdParser::GetBool (unsigned id) const { return fParser->FindArgById(id)->val.boolVal; } //=========================================================================== bool CCmdParser::GetBool (const wchar name[]) const { return fParser->FindArgByName(name)->val.boolVal; } //=========================================================================== float CCmdParser::GetFloat (unsigned id) const { return fParser->FindArgById(id)->val.floatVal; } //=========================================================================== float CCmdParser::GetFloat (const wchar name[]) const { return fParser->FindArgByName(name)->val.floatVal; } //=========================================================================== int CCmdParser::GetInt (unsigned id) const { return fParser->FindArgById(id)->val.intVal; } //=========================================================================== int CCmdParser::GetInt (const wchar name[]) const { return fParser->FindArgByName(name)->val.intVal; } //=========================================================================== const wchar * CCmdParser::GetString (unsigned id) const { return fParser->FindArgById(id)->val.strVal; } //=========================================================================== const wchar * CCmdParser::GetString (const wchar name[]) const { return fParser->FindArgByName(name)->val.strVal; } //=========================================================================== unsigned CCmdParser::GetUnsigned (unsigned id) const { return fParser->FindArgById(id)->val.unsignedVal; } //=========================================================================== unsigned CCmdParser::GetUnsigned (const wchar name[]) const { return fParser->FindArgByName(name)->val.unsignedVal; } //=========================================================================== void CCmdParser::Initialize (const CmdArgDef def[], unsigned defCount) { fParser = NEW(CICmdParser)(def, defCount); } //=========================================================================== bool CCmdParser::IsSpecified (unsigned id) const { if (const CmdArgData * data = fParser->FindArgById(id)) return data->isSpecified; return false; } //=========================================================================== bool CCmdParser::IsSpecified (const wchar name[]) const { if (const CmdArgData * data = fParser->FindArgByName(name)) return data->isSpecified; return false; } //=========================================================================== void CCmdParser::OnError (const wchar str[], ECmdError errorCode, const wchar arg[], const wchar value[]) { REF(str); REF(errorCode); REF(arg); REF(value); } //=========================================================================== bool CCmdParser::OnExtra (const wchar str[]) { REF(str); return false; } //=========================================================================== bool CCmdParser::Parse (const wchar cmdLine[]) { // If no command line was passed, use the application's command line, // skipping past the program name if (!cmdLine) { cmdLine = AppGetCommandLine(); StrTokenize(&cmdLine, nil, 0, WHITESPACE); while (*cmdLine == L' ') ++cmdLine; } // Process the command line CmdTokState state = { this, (unsigned)-1, // pending index 0 // unflagged index }; bool result; result = fParser->Tokenize(&state, cmdLine); if (result) result = fParser->CheckAllRequiredArguments(&state); return result; } /**************************************************************************** * * CCmdParserSimple * ***/ //=========================================================================== CCmdParserSimple::CCmdParserSimple ( unsigned requiredStringCount, unsigned optionalStringCount, const wchar flaggedBoolNames[] // double null terminated if used ) { // Count the number of flagged arguments unsigned flaggedBoolCount = 0; const wchar * curr; if (flaggedBoolNames) for (curr = flaggedBoolNames; *curr; curr += StrLen(curr) + 1) ++flaggedBoolCount; // Build the argument definition array unsigned totalCount = requiredStringCount + optionalStringCount + flaggedBoolCount; FARRAY(CmdArgDef) argDef(totalCount); unsigned index = 0; for (; index < requiredStringCount; ++index) { argDef[index].flags = kCmdArgRequired | kCmdTypeString; argDef[index].name = nil; argDef[index].id = index + 1; } for (; index < requiredStringCount + optionalStringCount; ++index) { argDef[index].flags = kCmdArgOptional | kCmdTypeString; argDef[index].name = nil; argDef[index].id = index + 1; } for (curr = flaggedBoolNames; index < totalCount; ++index) { argDef[index].flags = kCmdArgFlagged | kCmdTypeBool; argDef[index].name = curr; argDef[index].id = 0; curr += StrLen(curr) + 1; } // Initialize the parser Initialize(argDef.Ptr(), argDef.Count()); }