/*==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 . 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[]) { } //=========================================================================== bool CCmdParser::OnExtra (const wchar 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()); }