From b995135138493610e9191e92e6db2a7158c72647 Mon Sep 17 00:00:00 2001 From: leper Date: Mon, 17 Nov 2014 01:03:59 +0000 Subject: [PATCH] Use an FSM to parse our config files instead of using CParser. Refs #2589. Properly write config settings with multiple values back to files. Refs #1810. Print error messages if we encountered an invalid setting. This was SVN commit r15980. --- source/ps/ConfigDB.cpp | 317 +++++++++++-------- source/ps/ConfigDB.h | 28 +- source/ps/Hotkey.cpp | 95 ++---- source/ps/scripting/JSInterface_ConfigDB.cpp | 4 +- 4 files changed, 231 insertions(+), 213 deletions(-) diff --git a/source/ps/ConfigDB.cpp b/source/ps/ConfigDB.cpp index 34886e5356..dfb4d9ea42 100644 --- a/source/ps/ConfigDB.cpp +++ b/source/ps/ConfigDB.cpp @@ -22,11 +22,10 @@ #include "CLogger.h" #include "ConfigDB.h" #include "Filesystem.h" -#include "Parser.h" #include "ThreadUtil.h" #include "lib/allocators/shared_ptr.h" -typedef std::map TConfigMap; +typedef std::map TConfigMap; TConfigMap CConfigDB::m_Map[CFG_LAST]; VfsPath CConfigDB::m_ConfigFile[CFG_LAST]; @@ -47,48 +46,76 @@ CConfigDB::CConfigDB() ENSURE(err == 0); } -#define GETVAL(T, type) \ - void CConfigDB::GetValue##T(EConfigNamespace ns, const CStr& name, type& value) \ - { \ - if (ns < 0 || ns >= CFG_LAST) \ - { \ - debug_warn(L"CConfigDB: Invalid ns value"); \ - return; \ - } \ - CScopeLock s(&cfgdb_mutex); \ - TConfigMap::iterator it = m_Map[CFG_COMMAND].find(name); \ - if (it != m_Map[CFG_COMMAND].end()) \ - { \ - it->second[0].Get##T(value); \ - return; \ - } \ - \ - for (int search_ns = ns; search_ns >= 0; search_ns--) \ - { \ - it = m_Map[search_ns].find(name); \ - if (it != m_Map[search_ns].end()) \ - { \ - it->second[0].Get##T(value); \ - return; \ - } \ - } \ +#define CHECK_NS(rval)\ + do {\ + if (ns < 0 || ns >= CFG_LAST)\ + {\ + debug_warn(L"CConfigDB: Invalid ns value");\ + return rval;\ + }\ + } while (false) + +namespace { +template void Get(const CStr& value, T& ret) +{ + std::stringstream ss(value); + ss >> ret; +} +template<> void Get<>(const CStr& value, bool& ret) +{ + ret = value == "true"; +} +template<> void Get<>(const CStr& value, std::string& ret) +{ + ret = value; +} +std::string EscapeString(const CStr& str) +{ + std::string ret; + for (size_t i = 0; i < str.length(); ++i) + { + if (str[i] == '\\') + ret += "\\\\"; + else if (str[i] == '"') + ret += "\\\""; + else + ret += str[i]; } + return ret; +} +} // namespace -GETVAL(Bool, bool) -GETVAL(Int, int) -GETVAL(Float, float) -GETVAL(Double, double) -GETVAL(String, std::string) - +#define GETVAL(type)\ + void CConfigDB::GetValue(EConfigNamespace ns, const CStr& name, type& value)\ + {\ + CHECK_NS();\ + CScopeLock s(&cfgdb_mutex);\ + TConfigMap::iterator it = m_Map[CFG_COMMAND].find(name);\ + if (it != m_Map[CFG_COMMAND].end())\ + {\ + Get(it->second[0], value);\ + return;\ + }\ + for (int search_ns = ns; search_ns >= 0; search_ns--)\ + {\ + it = m_Map[search_ns].find(name);\ + if (it != m_Map[search_ns].end())\ + {\ + Get(it->second[0], value);\ + return;\ + }\ + }\ + } +GETVAL(bool) +GETVAL(int) +GETVAL(float) +GETVAL(double) +GETVAL(std::string) #undef GETVAL void CConfigDB::GetValues(EConfigNamespace ns, const CStr& name, CConfigValueSet& values) { - if (ns < 0 || ns >= CFG_LAST) - { - debug_warn(L"CConfigDB: Invalid ns value"); - return; - } + CHECK_NS(); CScopeLock s(&cfgdb_mutex); TConfigMap::iterator it = m_Map[CFG_COMMAND].find(name); @@ -111,11 +138,7 @@ void CConfigDB::GetValues(EConfigNamespace ns, const CStr& name, CConfigValueSet EConfigNamespace CConfigDB::GetValueNamespace(EConfigNamespace ns, const CStr& name) { - if (ns < 0 || ns >= CFG_LAST) - { - debug_warn(L"CConfigDB: Invalid ns value"); - return CFG_LAST; - } + CHECK_NS(CFG_LAST); CScopeLock s(&cfgdb_mutex); TConfigMap::iterator it = m_Map[CFG_COMMAND].find(name); @@ -137,11 +160,7 @@ std::map CConfigDB::GetValuesWithPrefix(EConfigNamespace CScopeLock s(&cfgdb_mutex); std::map ret; - if (ns < 0 || ns >= CFG_LAST) - { - debug_warn(L"CConfigDB: Invalid ns value"); - return ret; - } + CHECK_NS(ret); // Loop upwards so that values in later namespaces can override // values in earlier namespaces @@ -165,50 +184,32 @@ std::map CConfigDB::GetValuesWithPrefix(EConfigNamespace void CConfigDB::SetValueString(EConfigNamespace ns, const CStr& name, const CStr& value) { - if (ns < 0 || ns >= CFG_LAST) - { - debug_warn(L"CConfigDB: Invalid ns value"); - return; - } + CHECK_NS(); CScopeLock s(&cfgdb_mutex); TConfigMap::iterator it = m_Map[ns].find(name); if (it == m_Map[ns].end()) it = m_Map[ns].insert(m_Map[ns].begin(), make_pair(name, CConfigValueSet(1))); - it->second[0].m_String = value; + it->second[0] = value; } void CConfigDB::SetConfigFile(EConfigNamespace ns, const VfsPath& path) { - if (ns < 0 || ns >= CFG_LAST) - { - debug_warn(L"CConfigDB: Invalid ns value"); - return; - } + CHECK_NS(); CScopeLock s(&cfgdb_mutex); - m_ConfigFile[ns]=path; + m_ConfigFile[ns] = path; } bool CConfigDB::Reload(EConfigNamespace ns) { - if (ns < 0 || ns >= CFG_LAST) - { - debug_warn(L"CConfigDB: Invalid ns value"); - return false; - } + CHECK_NS(false); CScopeLock s(&cfgdb_mutex); - // Set up CParser - CParser parser; - CParserLine parserLine; - parser.InputTaskType("Assignment", "_$ident_=<_[-$arg(_minus)]_$value_,>_[-$arg(_minus)]_$value[[;]$rest]"); - parser.InputTaskType("CommentOrBlank", "_[;[$rest]]"); - - // Open file with VFS - shared_ptr buffer; size_t buflen; + shared_ptr buffer; + size_t buflen; { // Handle missing files quietly if (g_VFS->GetFileInfo(m_ConfigFile[ns], NULL) < 0) @@ -216,65 +217,119 @@ bool CConfigDB::Reload(EConfigNamespace ns) LOGMESSAGE(L"Cannot find config file \"%ls\" - ignoring", m_ConfigFile[ns].string().c_str()); return false; } - else + + LOGMESSAGE(L"Loading config file \"%ls\"", m_ConfigFile[ns].string().c_str()); + Status ret = g_VFS->LoadFile(m_ConfigFile[ns], buffer, buflen); + if (ret != INFO::OK) { - LOGMESSAGE(L"Loading config file \"%ls\"", m_ConfigFile[ns].string().c_str()); - Status ret = g_VFS->LoadFile(m_ConfigFile[ns], buffer, buflen); - if (ret != INFO::OK) - { - LOGERROR(L"CConfigDB::Reload(): vfs_load for \"%ls\" failed: return was %lld", m_ConfigFile[ns].string().c_str(), (long long)ret); - return false; - } + LOGERROR(L"CConfigDB::Reload(): vfs_load for \"%ls\" failed: return was %lld", m_ConfigFile[ns].string().c_str(), (long long)ret); + return false; } } TConfigMap newMap; + char *filebuf = (char*)buffer.get(); + char *filebufend = filebuf+buflen; - char *filebuf=(char *)buffer.get(); - char *filebufend=filebuf+buflen; - - // Read file line by line - char *next=filebuf-1; - do + bool quoted = false; + CStr name; + CStr value; + int line = 1; + std::vector values; + for (char* pos = filebuf; pos < filebufend; ++pos) { - char *pos=next+1; - next=(char *)memchr(pos, '\n', filebufend-pos); - if (!next) next=filebufend; - - char *lend=next; - if (lend > filebuf && *(lend-1) == '\r') lend--; - - // Send line to parser - bool parseOk=parserLine.ParseString(parser, std::string(pos, lend)); - // Get name and value from parser - std::string name; - std::string value; - - if (parseOk && - parserLine.GetArgCount()>=2 && - parserLine.GetArgString(0, name) && - parserLine.GetArgString(1, value)) + switch (*pos) { - // Add name and value to the map - size_t argCount = parserLine.GetArgCount(); + case '\n': + case ';': + break; // We finished parsing this line - newMap[name].clear(); + case ' ': + case '\r': + continue; // ignore - for( size_t t = 0; t < argCount; t++ ) + case '=': + // Parse parameters (comma separated, possibly quoted) + for (++pos; pos < filebufend && *pos != '\n' && *pos != ';'; ++pos) { - if( !parserLine.GetArgString( (int)t + 1, value ) ) - continue; - CConfigValue argument; - argument.m_String = value; - newMap[name].push_back( argument ); - if (name == "lobby.password") - value = "*******"; - LOGMESSAGE(L"Loaded config string \"%hs\" = \"%hs\"", name.c_str(), value.c_str()); + switch (*pos) + { + case '"': + quoted = true; + // parse until not quoted anymore + for (++pos; pos < filebufend && *pos != '\n' && *pos != '"'; ++pos) + { + if (*pos == '\\' && ++pos == filebufend) + { + LOGERROR(L"Escape character at end of input (line %d in '%ls')", line, m_ConfigFile[ns].string().c_str()); + break; + } + + value.push_back(*pos); + } + if (pos < filebufend && *pos == '"') + quoted = false; + else + --pos; // We should terminate the outer loop too + break; + + case '\r': + case ' ': + break; // ignore + + case ',': + if (!value.empty()) + values.push_back(value); + value.clear(); + break; + + default: + value.push_back(*pos); + break; + } + } + if (quoted) // We ignore the invalid parameter + LOGERROR(L"Unmatched quote while parsing config file '%ls' on line %d", m_ConfigFile[ns].string().c_str(), line); + else if (!value.empty()) + values.push_back(value); + value.clear(); + quoted = false; + break; // We are either at the end of the line, or we still have a comment to parse + + default: + name.push_back(*pos); + continue; + } + + // Consume the rest of the line + while (pos < filebufend && *pos != '\n') + ++pos; + // Store the setting + if (!name.empty() && !values.empty()) + { + newMap[name] = values; + if (name == "lobby.password") + LOGMESSAGE(L"Loaded config string \"%hs\"", name.c_str()); + else + { + std::string vals; + for (size_t i = 0; i < newMap[name].size() - 1; ++i) + vals += "\"" + EscapeString(newMap[name][i]) + "\", "; + vals += "\"" + EscapeString(newMap[name][values.size()-1]) + "\""; + LOGMESSAGE(L"Loaded config string \"%hs\" = %hs", name.c_str(), vals.c_str()); } } + else if (!name.empty()) + LOGERROR(L"Encountered config setting '%hs' without value while parsing '%ls' on line %d", name.c_str(), m_ConfigFile[ns].string().c_str(), line); + + name.clear(); + values.clear(); + ++line; } - while (next < filebufend); - + + if (!name.empty()) + LOGERROR(L"Config file does not have a new line after the last config setting '%hs'", name.c_str()); + m_Map[ns].swap(newMap); return true; @@ -282,11 +337,7 @@ bool CConfigDB::Reload(EConfigNamespace ns) bool CConfigDB::WriteFile(EConfigNamespace ns) { - if (ns < 0 || ns >= CFG_LAST) - { - debug_warn(L"CConfigDB: Invalid ns value"); - return false; - } + CHECK_NS(false); CScopeLock s(&cfgdb_mutex); return WriteFile(ns, m_ConfigFile[ns]); @@ -294,25 +345,25 @@ bool CConfigDB::WriteFile(EConfigNamespace ns) bool CConfigDB::WriteFile(EConfigNamespace ns, const VfsPath& path) { - if (ns < 0 || ns >= CFG_LAST) - { - debug_warn(L"CConfigDB: Invalid ns value"); - return false; - } + CHECK_NS(false); CScopeLock s(&cfgdb_mutex); shared_ptr buf; AllocateAligned(buf, 1*MiB, maxSectorSize); char* pos = (char*)buf.get(); - TConfigMap &map=m_Map[ns]; - for(TConfigMap::const_iterator it = map.begin(); it != map.end(); ++it) + TConfigMap &map = m_Map[ns]; + for (TConfigMap::const_iterator it = map.begin(); it != map.end(); ++it) { - pos += sprintf(pos, "%s = \"%s\"\n", it->first.c_str(), it->second[0].m_String.c_str()); + size_t i; + pos += sprintf(pos, "%s = ", it->first.c_str()); + for (i = 0; i < it->second.size() - 1; ++i) + pos += sprintf(pos, "\"%s\", ", EscapeString(it->second[i]).c_str()); + pos += sprintf(pos, "\"%s\"\n", EscapeString(it->second[i]).c_str()); } const size_t len = pos - (char*)buf.get(); Status ret = g_VFS->CreateFile(path, buf, len); - if(ret < 0) + if (ret < 0) { LOGERROR(L"CConfigDB::WriteFile(): CreateFile \"%ls\" failed (error: %d)", path.string().c_str(), (int)ret); return false; @@ -320,3 +371,5 @@ bool CConfigDB::WriteFile(EConfigNamespace ns, const VfsPath& path) return true; } + +#undef CHECK_NS diff --git a/source/ps/ConfigDB.h b/source/ps/ConfigDB.h index 1725e71685..585752e691 100644 --- a/source/ps/ConfigDB.h +++ b/source/ps/ConfigDB.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2013 Wildfire Games. +/* Copyright (C) 2014 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -27,7 +27,6 @@ #ifndef INCLUDED_CONFIGDB #define INCLUDED_CONFIGDB -#include "Parser.h" #include "CStr.h" #include "Singleton.h" @@ -46,14 +45,13 @@ enum EConfigNamespace CFG_LAST }; -typedef CParserValue CConfigValue; -typedef std::vector CConfigValueSet; +typedef std::vector CConfigValueSet; #define g_ConfigDB CConfigDB::GetSingleton() class CConfigDB: public Singleton { - static std::map m_Map[]; + static std::map m_Map[]; static VfsPath m_ConfigFile[]; public: @@ -64,15 +62,15 @@ public: * will search CFG_COMMAND first, and then all namespaces from the specified * namespace down. */ - void GetValueBool(EConfigNamespace ns, const CStr& name, bool& value); - ///@copydoc CConfigDB::GetValueBool - void GetValueInt(EConfigNamespace ns, const CStr& name, int& value); - ///@copydoc CConfigDB::GetValueBool - void GetValueFloat(EConfigNamespace ns, const CStr& name, float& value); - ///@copydoc CConfigDB::GetValueBool - void GetValueDouble(EConfigNamespace ns, const CStr& name, double& value); - ///@copydoc CConfigDB::GetValueBool - void GetValueString(EConfigNamespace ns, const CStr& name, std::string& value); + void GetValue(EConfigNamespace ns, const CStr& name, bool& value); + ///@copydoc CConfigDB::GetValue + void GetValue(EConfigNamespace ns, const CStr& name, int& value); + ///@copydoc CConfigDB::GetValue + void GetValue(EConfigNamespace ns, const CStr& name, float& value); + ///@copydoc CConfigDB::GetValue + void GetValue(EConfigNamespace ns, const CStr& name, double& value); + ///@copydoc CConfigDB::GetValue + void GetValue(EConfigNamespace ns, const CStr& name, std::string& value); /** * Attempt to retrieve a vector of values corresponding to the given setting; @@ -145,6 +143,6 @@ public: // convenience wrapper on top of CConfigValue::Get* simplifies user code and // avoids "assignment within condition expression" warnings. #define CFG_GET_VAL(name, type, destination)\ - g_ConfigDB.GetValue##type(CFG_USER, name, destination) + g_ConfigDB.GetValue(CFG_USER, name, destination) #endif diff --git a/source/ps/Hotkey.cpp b/source/ps/Hotkey.cpp index 99ad2f85b4..22ceeba593 100644 --- a/source/ps/Hotkey.cpp +++ b/source/ps/Hotkey.cpp @@ -18,6 +18,8 @@ #include "precompiled.h" #include "Hotkey.h" +#include + #include "lib/input.h" #include "ConfigDB.h" #include "CLogger.h" @@ -62,81 +64,46 @@ std::map g_HotkeyStatus; // all key combinations that trigger it. static void LoadConfigBindings() { - std::map bindings = g_ConfigDB.GetValuesWithPrefix( CFG_COMMAND, "hotkey." ); - - CParser multikeyParser; - multikeyParser.InputTaskType( "multikey", "<[~$arg(_negate)]$value_+_>_[~$arg(_negate)]$value" ); - - for( std::map::iterator bindingsIt = bindings.begin(); bindingsIt != bindings.end(); ++bindingsIt ) + std::map bindings = g_ConfigDB.GetValuesWithPrefix(CFG_COMMAND, "hotkey."); + for (std::map::iterator bindingsIt = bindings.begin(); bindingsIt != bindings.end(); ++bindingsIt) { std::string hotkeyName = bindingsIt->first.substr(7); // strip the "hotkey." prefix - - for( CConfigValueSet::iterator it = bindingsIt->second.begin(); it != bindingsIt->second.end(); ++it ) + for (CConfigValueSet::iterator it = bindingsIt->second.begin(); it != bindingsIt->second.end(); ++it) { - std::string hotkey; - if( it->GetString( hotkey ) ) + const CStr& hotkey = *it; + std::vector keyCombination; + + // Iterate through multiple-key bindings (e.g. Ctrl+I) + boost::char_separator sep("+"); + typedef boost::tokenizer > tokenizer; + tokenizer tok(hotkey, sep); + for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it) { - std::vector keyCombination; - - CParserLine multikeyIdentifier; - multikeyIdentifier.ParseString( multikeyParser, hotkey ); - - // Iterate through multiple-key bindings (e.g. Ctrl+I) - - bool negateNext = false; - - for( size_t t = 0; t < multikeyIdentifier.GetArgCount(); t++ ) + // Attempt decode as key name + int mapping = FindKeyCode(*it); + if (!mapping) { - - if( multikeyIdentifier.GetArgString( (int)t, hotkey ) ) - { - if( hotkey == "_negate" ) - { - negateNext = true; - continue; - } - - // Attempt decode as key name - int mapping = FindKeyCode( hotkey ); - - // Attempt to decode as a negation of a keyname - // Yes, it's going a bit far, perhaps. - // Too powerful for most uses, probably. - // However, it got some hardcoding out of the engine. - // Thus it makes me happy. - - if( !mapping ) - { - LOGWARNING(L"Hotkey mapping used invalid key '%hs'", hotkey.c_str() ); - continue; - } - - SKey key = { (SDLKEY)mapping, negateNext }; - keyCombination.push_back(key); - - negateNext = false; - - } + LOGWARNING(L"Hotkey mapping used invalid key '%hs'", hotkey.c_str()); + continue; } - std::vector::iterator itKey, itKey2; + SKey key = { (SDLKEY)mapping, false }; + keyCombination.push_back(key); + } - for( itKey = keyCombination.begin(); itKey != keyCombination.end(); ++itKey ) - { - SHotkeyMapping bindCode; + std::vector::iterator itKey, itKey2; + for (itKey = keyCombination.begin(); itKey != keyCombination.end(); ++itKey) + { + SHotkeyMapping bindCode; - bindCode.name = hotkeyName; - bindCode.negated = itKey->negated; + bindCode.name = hotkeyName; + bindCode.negated = itKey->negated; - for( itKey2 = keyCombination.begin(); itKey2 != keyCombination.end(); ++itKey2 ) - { - // Push any auxiliary keys. - if( itKey != itKey2 ) - bindCode.requires.push_back( *itKey2 ); - } + for (itKey2 = keyCombination.begin(); itKey2 != keyCombination.end(); ++itKey2) + if (itKey != itKey2) // Push any auxiliary keys + bindCode.requires.push_back(*itKey2); - g_HotkeyMap[itKey->code].push_back( bindCode ); - } + g_HotkeyMap[itKey->code].push_back(bindCode); } } } diff --git a/source/ps/scripting/JSInterface_ConfigDB.cpp b/source/ps/scripting/JSInterface_ConfigDB.cpp index a02e47659c..43c6a4d8a6 100644 --- a/source/ps/scripting/JSInterface_ConfigDB.cpp +++ b/source/ps/scripting/JSInterface_ConfigDB.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2013 Wildfire Games. +/* Copyright (C) 2014 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -49,7 +49,7 @@ std::string JSI_ConfigDB::GetValue(ScriptInterface::CxPrivate* UNUSED(pCxPrivate return std::string(); std::string value; - g_ConfigDB.GetValueString(cfgNs, name, value); + g_ConfigDB.GetValue(cfgNs, name, value); return value; }