1
1
forked from 0ad/0ad

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.
This commit is contained in:
leper 2014-11-17 01:03:59 +00:00
parent 334f391446
commit b995135138
4 changed files with 231 additions and 213 deletions

View File

@ -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 <CStr, CConfigValueSet> TConfigMap;
typedef std::map<CStr, CConfigValueSet> 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<typename T> 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<CStr, CConfigValueSet> CConfigDB::GetValuesWithPrefix(EConfigNamespace
CScopeLock s(&cfgdb_mutex);
std::map<CStr, CConfigValueSet> 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<CStr, CConfigValueSet> 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<u8> buffer; size_t buflen;
shared_ptr<u8> 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<CStr> 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<u8> 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

View File

@ -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<CParserValue> CConfigValueSet;
typedef std::vector<CStr> CConfigValueSet;
#define g_ConfigDB CConfigDB::GetSingleton()
class CConfigDB: public Singleton<CConfigDB>
{
static std::map <CStr, CConfigValueSet> m_Map[];
static std::map<CStr, CConfigValueSet> 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

View File

@ -18,6 +18,8 @@
#include "precompiled.h"
#include "Hotkey.h"
#include <boost/tokenizer.hpp>
#include "lib/input.h"
#include "ConfigDB.h"
#include "CLogger.h"
@ -62,81 +64,46 @@ std::map<std::string, bool> g_HotkeyStatus;
// all key combinations that trigger it.
static void LoadConfigBindings()
{
std::map<CStr, CConfigValueSet> bindings = g_ConfigDB.GetValuesWithPrefix( CFG_COMMAND, "hotkey." );
CParser multikeyParser;
multikeyParser.InputTaskType( "multikey", "<[~$arg(_negate)]$value_+_>_[~$arg(_negate)]$value" );
for( std::map<CStr, CConfigValueSet>::iterator bindingsIt = bindings.begin(); bindingsIt != bindings.end(); ++bindingsIt )
std::map<CStr, CConfigValueSet> bindings = g_ConfigDB.GetValuesWithPrefix(CFG_COMMAND, "hotkey.");
for (std::map<CStr, CConfigValueSet>::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<SKey> keyCombination;
// Iterate through multiple-key bindings (e.g. Ctrl+I)
boost::char_separator<char> sep("+");
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
tokenizer tok(hotkey, sep);
for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it)
{
std::vector<SKey> 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<SKey>::iterator itKey, itKey2;
SKey key = { (SDLKEY)mapping, false };
keyCombination.push_back(key);
}
for( itKey = keyCombination.begin(); itKey != keyCombination.end(); ++itKey )
{
SHotkeyMapping bindCode;
std::vector<SKey>::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);
}
}
}

View File

@ -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;
}