mirror of
https://github.com/RetroDECK/Supermodel.git
synced 2024-11-22 05:45:38 +00:00
Moved logging functions into OSD/Logger.cpp (eventually logging system will be reworked entirely) and added new config tree structure (not yet used).
This commit is contained in:
parent
0650ffc37c
commit
bcc663d4eb
|
@ -119,7 +119,8 @@ endif
|
||||||
#
|
#
|
||||||
# Objects and Dependencies
|
# Objects and Dependencies
|
||||||
#
|
#
|
||||||
OBJ = $(OBJ_DIR)/PPCDisasm.o $(OBJ_DIR)/Games.o $(OBJ_DIR)/Config.o $(OBJ_DIR)/INIFile.o $(OBJ_DIR)/BlockFile.o $(OBJ_DIR)/93C46.o \
|
OBJ = $(OBJ_DIR)/Format.o $(OBJ_DIR)/NewConfig.o \
|
||||||
|
$(OBJ_DIR)/PPCDisasm.o $(OBJ_DIR)/Games.o $(OBJ_DIR)/Config.o $(OBJ_DIR)/INIFile.o $(OBJ_DIR)/BlockFile.o $(OBJ_DIR)/93C46.o \
|
||||||
$(OBJ_DIR)/ROMLoad.o $(OBJ_DIR)/unzip.o $(OBJ_DIR)/ioapi.o $(OBJ_DIR)/Error.o $(OBJ_DIR)/glew.o $(OBJ_DIR)/Shader.o \
|
$(OBJ_DIR)/ROMLoad.o $(OBJ_DIR)/unzip.o $(OBJ_DIR)/ioapi.o $(OBJ_DIR)/Error.o $(OBJ_DIR)/glew.o $(OBJ_DIR)/Shader.o \
|
||||||
$(OBJ_DIR)/Real3D.o $(OBJ_DIR)/Legacy3D.o $(OBJ_DIR)/Models.o $(OBJ_DIR)/TextureRefs.o \
|
$(OBJ_DIR)/Real3D.o $(OBJ_DIR)/Legacy3D.o $(OBJ_DIR)/Models.o $(OBJ_DIR)/TextureRefs.o \
|
||||||
$(OBJ_DIR)/New3D.o $(OBJ_DIR)/Mat4.o $(OBJ_DIR)/Model.o $(OBJ_DIR)/PolyHeader.o $(OBJ_DIR)/Texture.o $(OBJ_DIR)/TextureSheet.o $(OBJ_DIR)/VBO.o $(OBJ_DIR)/Vec.o $(OBJ_DIR)/R3DShader.o $(OBJ_DIR)/R3DFloat.o \
|
$(OBJ_DIR)/New3D.o $(OBJ_DIR)/Mat4.o $(OBJ_DIR)/Model.o $(OBJ_DIR)/PolyHeader.o $(OBJ_DIR)/Texture.o $(OBJ_DIR)/TextureSheet.o $(OBJ_DIR)/VBO.o $(OBJ_DIR)/Vec.o $(OBJ_DIR)/R3DShader.o $(OBJ_DIR)/R3DFloat.o \
|
||||||
|
@ -135,7 +136,7 @@ OBJ = $(OBJ_DIR)/PPCDisasm.o $(OBJ_DIR)/Games.o $(OBJ_DIR)/Config.o $(OBJ_DIR)/I
|
||||||
$(OBJ_DIR)/amp_layer2.o $(OBJ_DIR)/amp_layer3.o $(OBJ_DIR)/amp_misc2.o $(OBJ_DIR)/amp_position.o $(OBJ_DIR)/amp_transform.o \
|
$(OBJ_DIR)/amp_layer2.o $(OBJ_DIR)/amp_layer3.o $(OBJ_DIR)/amp_misc2.o $(OBJ_DIR)/amp_position.o $(OBJ_DIR)/amp_transform.o \
|
||||||
$(OBJ_DIR)/amp_util.o \
|
$(OBJ_DIR)/amp_util.o \
|
||||||
$(OBJ_DIR)/Crypto.o \
|
$(OBJ_DIR)/Crypto.o \
|
||||||
$(OBJ_DIR)/Format.o
|
$(OBJ_DIR)/Logger.o
|
||||||
|
|
||||||
|
|
||||||
# If built-in debugger enabled, include all debugging classes
|
# If built-in debugger enabled, include all debugging classes
|
||||||
|
@ -157,6 +158,10 @@ ppcd: $(BIN_DIR) $(OBJ_DIR)
|
||||||
$(CXX) Src/CPU/PowerPC/PPCDisasm.cpp $(CPPFLAGS) -DSTANDALONE -o $(OBJ_DIR)/ppcd.o
|
$(CXX) Src/CPU/PowerPC/PPCDisasm.cpp $(CPPFLAGS) -DSTANDALONE -o $(OBJ_DIR)/ppcd.o
|
||||||
$(LD) -o $(BIN_DIR)/ppcd.exe -mconsole $(OBJ_DIR)/ppcd.o
|
$(LD) -o $(BIN_DIR)/ppcd.exe -mconsole $(OBJ_DIR)/ppcd.o
|
||||||
|
|
||||||
|
tests: $(BIN_DIR) $(OBJ_DIR) $(OBJ) Src/Util/Test_Config.cpp
|
||||||
|
$(CXX) Src/Util/Test_Config.cpp $(CPPFLAGS) -o $(OBJ_DIR)/Test_Config.o
|
||||||
|
$(LD) -o $(BIN_DIR)/test_config.exe -mconsole $(OBJ_DIR)/Test_Config.o $(OBJ_DIR)/Format.o $(OBJ_DIR)/NewConfig.o $(OBJ_DIR)/Logger.o
|
||||||
|
|
||||||
$(BIN_DIR):
|
$(BIN_DIR):
|
||||||
mkdir $(BIN_DIR)
|
mkdir $(BIN_DIR)
|
||||||
|
|
||||||
|
|
45
Src/OSD/Logger.cpp
Normal file
45
Src/OSD/Logger.cpp
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
#include "OSD/Logger.h"
|
||||||
|
|
||||||
|
// Logger object is used to redirect log messages appropriately
|
||||||
|
static CLogger *s_Logger = NULL;
|
||||||
|
|
||||||
|
CLogger *GetLogger()
|
||||||
|
{
|
||||||
|
return s_Logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetLogger(CLogger *Logger)
|
||||||
|
{
|
||||||
|
s_Logger = Logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugLog(const char *fmt, ...)
|
||||||
|
{
|
||||||
|
if (s_Logger == NULL)
|
||||||
|
return;
|
||||||
|
va_list vl;
|
||||||
|
va_start(vl, fmt);
|
||||||
|
s_Logger->DebugLog(fmt, vl);
|
||||||
|
va_end(vl);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InfoLog(const char *fmt, ...)
|
||||||
|
{
|
||||||
|
if (s_Logger == NULL)
|
||||||
|
return;
|
||||||
|
va_list vl;
|
||||||
|
va_start(vl, fmt);
|
||||||
|
s_Logger->InfoLog(fmt, vl);
|
||||||
|
va_end(vl);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ErrorLog(const char *fmt, ...)
|
||||||
|
{
|
||||||
|
if (s_Logger == NULL)
|
||||||
|
return FAIL;
|
||||||
|
va_list vl;
|
||||||
|
va_start(vl, fmt);
|
||||||
|
s_Logger->ErrorLog(fmt, vl);
|
||||||
|
va_end(vl);
|
||||||
|
return FAIL;
|
||||||
|
}
|
|
@ -239,8 +239,8 @@ private:
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
Log Functions
|
Log Functions
|
||||||
|
|
||||||
Message logging interface. All messages are passed by the OSD layer to the
|
Message logging interface. All messages are passed to the currently active
|
||||||
currently active logger object.
|
logger object.
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -63,59 +63,10 @@
|
||||||
#include "WinOutputs.h"
|
#include "WinOutputs.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
/******************************************************************************
|
|
||||||
Error and Debug Logging
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
// Log file names
|
// Log file names
|
||||||
#define DEBUG_LOG_FILE "debug.log"
|
#define DEBUG_LOG_FILE "debug.log"
|
||||||
#define ERROR_LOG_FILE "error.log"
|
#define ERROR_LOG_FILE "error.log"
|
||||||
|
|
||||||
// Logger object is used to redirect log messages appropriately
|
|
||||||
static CLogger *s_Logger = NULL;
|
|
||||||
|
|
||||||
CLogger *GetLogger()
|
|
||||||
{
|
|
||||||
return s_Logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetLogger(CLogger *Logger)
|
|
||||||
{
|
|
||||||
s_Logger = Logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DebugLog(const char *fmt, ...)
|
|
||||||
{
|
|
||||||
if (s_Logger == NULL)
|
|
||||||
return;
|
|
||||||
va_list vl;
|
|
||||||
va_start(vl, fmt);
|
|
||||||
s_Logger->DebugLog(fmt, vl);
|
|
||||||
va_end(vl);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InfoLog(const char *fmt, ...)
|
|
||||||
{
|
|
||||||
if (s_Logger == NULL)
|
|
||||||
return;
|
|
||||||
va_list vl;
|
|
||||||
va_start(vl, fmt);
|
|
||||||
s_Logger->InfoLog(fmt, vl);
|
|
||||||
va_end(vl);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ErrorLog(const char *fmt, ...)
|
|
||||||
{
|
|
||||||
if (s_Logger == NULL)
|
|
||||||
return FAIL;
|
|
||||||
va_list vl;
|
|
||||||
va_start(vl, fmt);
|
|
||||||
s_Logger->ErrorLog(fmt, vl);
|
|
||||||
va_end(vl);
|
|
||||||
return FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
Display Management
|
Display Management
|
||||||
|
@ -1549,9 +1500,9 @@ static void PrintGameList(void)
|
||||||
*
|
*
|
||||||
* Program entry point.
|
* Program entry point.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
|
|
||||||
#ifdef SUPERMODEL_DEBUGGER
|
#ifdef SUPERMODEL_DEBUGGER
|
||||||
bool cmdEnterDebugger = false;
|
bool cmdEnterDebugger = false;
|
||||||
#endif // SUPERMODEL_DEBUGGER
|
#endif // SUPERMODEL_DEBUGGER
|
||||||
|
|
|
@ -1,10 +1,33 @@
|
||||||
#include "Util/Format.h"
|
#include "Util/Format.h"
|
||||||
|
#include <cctype>
|
||||||
|
|
||||||
namespace Util
|
namespace Util
|
||||||
{
|
{
|
||||||
|
std::string TrimWhiteSpace(const std::string &str)
|
||||||
|
{
|
||||||
|
if (str.empty())
|
||||||
|
return std::string();
|
||||||
|
size_t first = 0;
|
||||||
|
for (; first < str.length(); ++first)
|
||||||
|
{
|
||||||
|
if (!isspace(str[first]))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (first >= str.length())
|
||||||
|
return std::string();
|
||||||
|
size_t last = str.length() - 1;
|
||||||
|
for (; last > first; --last)
|
||||||
|
{
|
||||||
|
if (!isspace(str[last]))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++last;
|
||||||
|
return std::string(str.c_str() + first, last - first);
|
||||||
|
}
|
||||||
|
|
||||||
static const char hex_digits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
static const char hex_digits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
||||||
|
|
||||||
const std::string Hex(uint32_t n, size_t num_digits)
|
std::string Hex(uint32_t n, size_t num_digits)
|
||||||
{
|
{
|
||||||
Util::Format f;
|
Util::Format f;
|
||||||
f << "0x";
|
f << "0x";
|
||||||
|
@ -16,17 +39,17 @@ namespace Util
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string Hex(uint32_t n)
|
std::string Hex(uint32_t n)
|
||||||
{
|
{
|
||||||
return Hex(n, 8);
|
return Hex(n, 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string Hex(uint16_t n)
|
std::string Hex(uint16_t n)
|
||||||
{
|
{
|
||||||
return Hex(n, 4);
|
return Hex(n, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string Hex(uint8_t n)
|
std::string Hex(uint8_t n)
|
||||||
{
|
{
|
||||||
return Hex(n, 2);
|
return Hex(n, 2);
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,10 +84,11 @@ namespace Util
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const std::string Hex(uint32_t n, size_t num_digits);
|
std::string TrimWhiteSpace(const std::string &str);
|
||||||
const std::string Hex(uint32_t n);
|
std::string Hex(uint32_t n, size_t num_digits);
|
||||||
const std::string Hex(uint16_t n);
|
std::string Hex(uint32_t n);
|
||||||
const std::string Hex(uint8_t n);
|
std::string Hex(uint16_t n);
|
||||||
|
std::string Hex(uint8_t n);
|
||||||
} // Util
|
} // Util
|
||||||
|
|
||||||
#endif // INCLUDED_FORMAT_H
|
#endif // INCLUDED_FORMAT_H
|
||||||
|
|
372
Src/Util/NewConfig.cpp
Normal file
372
Src/Util/NewConfig.cpp
Normal file
|
@ -0,0 +1,372 @@
|
||||||
|
/*
|
||||||
|
* Config Tree
|
||||||
|
* -----------
|
||||||
|
*
|
||||||
|
* A hierarchical data structure supporting arbitrary nesting. Each node
|
||||||
|
* (Config::Node) has a key and either a value or children (in fact, it may
|
||||||
|
* have both, but this does not make semantic sense and so the config tree
|
||||||
|
* builders take care not to allow it).
|
||||||
|
*
|
||||||
|
* All values are simply strings for now. They may have defaults, which are
|
||||||
|
* actually supplied by the caller and returned when a queried node cannot be
|
||||||
|
* found.
|
||||||
|
*
|
||||||
|
* Nodes at the same nesting level (siblings) are strung together in a
|
||||||
|
* linked list. Parents also maintain pointers to the first and last of their
|
||||||
|
* children (for order-preserving iteration) as well as a map for direct
|
||||||
|
* lookup by key.
|
||||||
|
*
|
||||||
|
* Keys may be reused at a given level. Key-value pairs will have their order
|
||||||
|
* preserved when iterated but only the most recent key is returned in direct
|
||||||
|
* map lookups.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* <game name="scud">
|
||||||
|
* <roms>
|
||||||
|
* <crom name="foo.bin" offset=0/>
|
||||||
|
* <crom name="bar.bin" offset=2/>
|
||||||
|
* </roms>
|
||||||
|
*
|
||||||
|
* config["game/roms"].Value() <-- should return nothing
|
||||||
|
* config["game/roms/crom/offset"].Value() <-- "2", the second <crom> tag
|
||||||
|
* config["game/roms"].begin() <-- iterate over two <crom> tags
|
||||||
|
*
|
||||||
|
* INI Semantics
|
||||||
|
* -------------
|
||||||
|
*
|
||||||
|
* INI files are linear in nature and divided into sections. Config trees built
|
||||||
|
* to represent INI files must adhere to the following rules:
|
||||||
|
*
|
||||||
|
* - Section nodes have their key set to the section name and value empty.
|
||||||
|
* They are the only nodes that can have children (i.e., IsLeaf() == false,
|
||||||
|
* HasChildren() == true).
|
||||||
|
* - Top-level node in the tree is the global section, and its key is
|
||||||
|
* "Global".
|
||||||
|
* - Only the global section (top-level section) may have child nodes that are
|
||||||
|
* sections. The config tree can therefore only be up to 2 levels deep: the
|
||||||
|
* first, top-most level consists of global parameters, and section nodes
|
||||||
|
* provide one more level of nesting.
|
||||||
|
*
|
||||||
|
* INI files contain zero or more sections followed by zero or more settings
|
||||||
|
* each.
|
||||||
|
*
|
||||||
|
* Setting0 = 100
|
||||||
|
* [ Global ]
|
||||||
|
* Setting1 = 200
|
||||||
|
* [ Section1 ]
|
||||||
|
* SettingA = 1 ; this is a comment
|
||||||
|
* SettingB = foo
|
||||||
|
* [ Section2 ]
|
||||||
|
* SettingX = bar
|
||||||
|
* SettingZ = "baz" ; quotes are optional and are stripped off during parsing
|
||||||
|
*
|
||||||
|
* Any setting not explicitly part of a section is assigned to the "Global"
|
||||||
|
* section. For example, Setting0 and Setting1 above are both part of "Global".
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Util/NewConfig.h"
|
||||||
|
#include "OSD/Logger.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace Util
|
||||||
|
{
|
||||||
|
namespace Config
|
||||||
|
{
|
||||||
|
Node Node::s_empty_node;
|
||||||
|
|
||||||
|
const Node &Node::operator[](const std::string &path) const
|
||||||
|
{
|
||||||
|
const Node *e = this;
|
||||||
|
std::vector<std::string> keys = Util::Format(path).Split('/');
|
||||||
|
for (auto &key: keys)
|
||||||
|
{
|
||||||
|
auto it = e->m_children.find(key);
|
||||||
|
if (it == e->m_children.end())
|
||||||
|
return s_empty_node;
|
||||||
|
e = it->second.get();
|
||||||
|
}
|
||||||
|
return *e;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node &Node::Get(const std::string &path)
|
||||||
|
{
|
||||||
|
return const_cast<Node &>(operator[](path));
|
||||||
|
}
|
||||||
|
|
||||||
|
const Node &Node::Get(const std::string &path) const
|
||||||
|
{
|
||||||
|
return operator[](path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Node::Print(size_t indent_level) const
|
||||||
|
{
|
||||||
|
std::fill_n(std::ostream_iterator<char>(std::cout), 2 * indent_level, ' ');
|
||||||
|
std::cout << m_key;
|
||||||
|
if (m_value.length())
|
||||||
|
std::cout << '=' << m_value;
|
||||||
|
std::cout << " children={";
|
||||||
|
for (auto v: m_children)
|
||||||
|
std::cout << ' ' << v.first;
|
||||||
|
std::cout << " }" << std::endl;
|
||||||
|
for (Ptr_t child = m_first_child; child; child = child->m_next_sibling)
|
||||||
|
child->Print(indent_level + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Node &Node::Create(const std::string &key)
|
||||||
|
{
|
||||||
|
Ptr_t node = std::make_shared<Node>(key);
|
||||||
|
AddChild(node);
|
||||||
|
return *node;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node &Node::Create(const std::string &key, const std::string &value)
|
||||||
|
{
|
||||||
|
Ptr_t node = std::make_shared<Node>(key, value);
|
||||||
|
AddChild(node);
|
||||||
|
return *node;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a newly-created node (which, among other things, implies no
|
||||||
|
// children) as a child
|
||||||
|
void Node::AddChild(Ptr_t &node)
|
||||||
|
{
|
||||||
|
if (!m_last_child)
|
||||||
|
{
|
||||||
|
m_first_child = node;
|
||||||
|
m_last_child = node;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_last_child->m_next_sibling = node;
|
||||||
|
m_last_child = node;
|
||||||
|
}
|
||||||
|
m_children[node->m_key] = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
void Node::Clear()
|
||||||
|
{
|
||||||
|
m_next_sibling.reset();
|
||||||
|
m_first_child.reset();
|
||||||
|
m_last_child.reset();
|
||||||
|
m_children.clear();
|
||||||
|
m_key.clear();
|
||||||
|
m_value.clear();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
Node::Node()
|
||||||
|
{
|
||||||
|
//std::cout << "Created " << "<null>" << " (" << this << ")" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node::Node(const std::string &key)
|
||||||
|
: m_key(key)
|
||||||
|
{
|
||||||
|
//std::cout << "Created " << key << " (" << this << ")" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node::Node(const std::string &key, const std::string &value)
|
||||||
|
: m_key(key),
|
||||||
|
m_value(value)
|
||||||
|
{
|
||||||
|
//std::cout << "Created " << key << '=' << value << " (" << this << ")" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deep copy
|
||||||
|
Node::Node(const Node &that)
|
||||||
|
: m_key(that.m_key),
|
||||||
|
m_value(that.m_value)
|
||||||
|
{
|
||||||
|
for (Ptr_t child = that.m_first_child; child; child = child->m_next_sibling)
|
||||||
|
{
|
||||||
|
Ptr_t copied_child = std::make_shared<Node>(*child);
|
||||||
|
AddChild(copied_child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Node::~Node()
|
||||||
|
{
|
||||||
|
//std::cout << "Destroyed " << m_key << " (" << this << ")" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node::Ptr_t CreateEmpty()
|
||||||
|
{
|
||||||
|
return std::shared_ptr<Node>(new Node());
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string StripComment(const std::string &line)
|
||||||
|
{
|
||||||
|
// Find first semicolon not enclosed in ""
|
||||||
|
bool inside_quotes = false;
|
||||||
|
for (auto it = line.begin(); it != line.end(); ++it)
|
||||||
|
{
|
||||||
|
inside_quotes ^= (*it == '\"');
|
||||||
|
if (!inside_quotes && *it == ';')
|
||||||
|
return std::string(line.begin(), it);
|
||||||
|
}
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ParseAssignment(Node ¤t_section, const std::string &filename, size_t line_num, const std::string &line)
|
||||||
|
{
|
||||||
|
size_t idx_equals = line.find_first_of('=');
|
||||||
|
if (idx_equals == std::string::npos)
|
||||||
|
{
|
||||||
|
//std::cerr << filename << ':' << line_num << ": Syntax error. No '=' found." << std::endl;
|
||||||
|
ErrorLog("%s:%d: Syntax error. No '=' found.", filename.c_str(), line_num);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::string lvalue(TrimWhiteSpace(std::string(line.begin(), line.begin() + idx_equals)));
|
||||||
|
if (lvalue.empty())
|
||||||
|
{
|
||||||
|
//std::cerr << filename << ':' << line_num << ": Syntax error. Setting name missing before '='." << std::endl;
|
||||||
|
ErrorLog("%s:%d: Syntax error. Setting name missing before '='.", filename.c_str(), line_num);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Get rvalue, which is allowed to be empty and strip off quotes if they
|
||||||
|
// exist. Only a single pair of quotes encasing the rvalue are permitted.
|
||||||
|
std::string rvalue(TrimWhiteSpace(std::string(line.begin() + idx_equals + 1, line.end())));
|
||||||
|
size_t idx_first_quote = rvalue.find_first_of('\"');
|
||||||
|
size_t idx_last_quote = rvalue.find_last_of('\"');
|
||||||
|
if (idx_first_quote == 0 && idx_last_quote == (rvalue.length() - 1) && idx_first_quote != idx_last_quote)
|
||||||
|
rvalue = std::string(rvalue.begin() + idx_first_quote + 1, rvalue.begin() + idx_last_quote);
|
||||||
|
if (std::count(rvalue.begin(), rvalue.end(), '\"') != 0)
|
||||||
|
{
|
||||||
|
//std::cerr << filename << ':' << line_num << ": Warning: Extraneous quotes present on line." << std::endl;
|
||||||
|
ErrorLog("%s:%d: Warning: Extraneous quotes present on line.", filename.c_str(), line_num);
|
||||||
|
}
|
||||||
|
// In INI files, we do not allow multiple settings with the same key. If
|
||||||
|
// a setting is specified multiple times, previous ones are overwritten.
|
||||||
|
if (current_section[lvalue].Empty())
|
||||||
|
current_section.Create(lvalue, rvalue);
|
||||||
|
else
|
||||||
|
current_section.Get(lvalue).SetValue(rvalue);
|
||||||
|
}
|
||||||
|
|
||||||
|
Node::Ptr_t FromINIFile(const std::string &filename)
|
||||||
|
{
|
||||||
|
std::ifstream file;
|
||||||
|
file.open(filename);
|
||||||
|
if (file.fail())
|
||||||
|
{
|
||||||
|
ErrorLog("Unable to open '%s'. Configuration will not be loaded.", filename.c_str());
|
||||||
|
return CreateEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
Node::Ptr_t global_ptr = std::make_shared<Node>("Global"); // the root is also the "Global" section
|
||||||
|
Node &global = *global_ptr;
|
||||||
|
Node *current_section = &global;
|
||||||
|
|
||||||
|
size_t line_num = 1;
|
||||||
|
while (!file.eof())
|
||||||
|
{
|
||||||
|
if (file.fail())
|
||||||
|
{
|
||||||
|
ErrorLog("%s:%d: File read error. Configuration will be incomplete.");
|
||||||
|
return CreateEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
std::getline(file, line);
|
||||||
|
line = StripComment(line);
|
||||||
|
line = TrimWhiteSpace(line);
|
||||||
|
|
||||||
|
if (!line.empty())
|
||||||
|
{
|
||||||
|
if (*line.begin() == '[' && *line.rbegin() == ']')
|
||||||
|
{
|
||||||
|
// Check if section exists else create a new one
|
||||||
|
std::string section(TrimWhiteSpace(std::string(line.begin() + 1, line.begin() + line.length() - 1)));
|
||||||
|
if (section.empty() || section == "Global") // empty section names (e.g., "[]") assigned to "[ Global ]"
|
||||||
|
current_section = &global;
|
||||||
|
else if (global[section].Empty())
|
||||||
|
{
|
||||||
|
Node &new_section = global.Create(section);
|
||||||
|
current_section = &new_section;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
current_section = &global.Get(section);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ParseAssignment(*current_section, filename, line_num, line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
line_num++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return global_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Produces a new INI section by merging two existing sections.
|
||||||
|
*
|
||||||
|
* - Nodes from x and y with children are ignored as per INI semantics.
|
||||||
|
* The presence of children indicates a section, not a setting.
|
||||||
|
* - Settings from y override settings in x if already present.
|
||||||
|
* - x's key is retained.
|
||||||
|
*/
|
||||||
|
Node::Ptr_t MergeINISections(const Node &x, const Node &y)
|
||||||
|
{
|
||||||
|
Node::Ptr_t merged_ptr = std::make_shared<Node>(x.Key()); // result has same key as x
|
||||||
|
Node &merged = *merged_ptr;
|
||||||
|
// First copy settings from section x
|
||||||
|
for (auto it = x.begin(); it != x.end(); ++it)
|
||||||
|
{
|
||||||
|
if (it->IsLeaf())
|
||||||
|
merged.Create(it->Key(), it->Value());
|
||||||
|
}
|
||||||
|
// Merge in settings from section y
|
||||||
|
for (auto it = y.begin(); it != y.end(); ++it)
|
||||||
|
{
|
||||||
|
auto &key = it->Key();
|
||||||
|
auto &value = it->Value();
|
||||||
|
if (it->IsLeaf())
|
||||||
|
{
|
||||||
|
if (merged[key].Empty())
|
||||||
|
merged.Create(key, value);
|
||||||
|
else
|
||||||
|
merged.Get(key).SetValue(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return merged_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WriteSection(std::ofstream &file, const Node §ion)
|
||||||
|
{
|
||||||
|
file << "[ " << section.Key() << " ]" << std::endl;
|
||||||
|
file << std::endl;
|
||||||
|
for (auto it = section.begin(); it != section.end(); ++it)
|
||||||
|
{
|
||||||
|
if (it->IsLeaf())
|
||||||
|
file << it->Key() << " = \"" << it->Value() << "\"" << std::endl;
|
||||||
|
}
|
||||||
|
file << std::endl << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteINIFile(const std::string &filename, const Node &config, const std::string &header_comment)
|
||||||
|
{
|
||||||
|
std::ofstream file;
|
||||||
|
file.open(filename);
|
||||||
|
if (file.fail())
|
||||||
|
{
|
||||||
|
ErrorLog("Unable to write to '%s'. Configuration will not be saved.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!header_comment.empty())
|
||||||
|
file << header_comment << std::endl << std::endl;
|
||||||
|
WriteSection(file, config);
|
||||||
|
for (auto it = config.begin(); it != config.end(); ++it)
|
||||||
|
{
|
||||||
|
if (it->HasChildren())
|
||||||
|
WriteSection(file, *it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // Config
|
||||||
|
} // Util
|
||||||
|
|
204
Src/Util/NewConfig.h
Normal file
204
Src/Util/NewConfig.h
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
#ifndef INCLUDED_UTIL_CONFIG_H
|
||||||
|
#define INCLUDED_UTIL_CONFIG_H
|
||||||
|
|
||||||
|
#include "Util/Format.h"
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <iterator>
|
||||||
|
|
||||||
|
namespace Util
|
||||||
|
{
|
||||||
|
namespace Config
|
||||||
|
{
|
||||||
|
class Node
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef std::shared_ptr<Node> Ptr_t;
|
||||||
|
typedef std::shared_ptr<const Node> ConstPtr_t;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ptr_t m_next_sibling;
|
||||||
|
Ptr_t m_first_child;
|
||||||
|
Ptr_t m_last_child;
|
||||||
|
std::map<std::string, Ptr_t> m_children;
|
||||||
|
const std::string m_key;
|
||||||
|
std::string m_value;
|
||||||
|
static Node s_empty_node; // key, value, and children must always be empty
|
||||||
|
|
||||||
|
void AddChild(Ptr_t &node);
|
||||||
|
Node(); // prohibit accidental/unintentional creation of blank nodes
|
||||||
|
friend Ptr_t CreateEmpty();
|
||||||
|
|
||||||
|
public:
|
||||||
|
class const_iterator;
|
||||||
|
|
||||||
|
class iterator: public std::iterator<std::forward_iterator_tag, Node>
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
Ptr_t m_node;
|
||||||
|
friend class const_iterator;
|
||||||
|
public:
|
||||||
|
inline iterator(Ptr_t node = Ptr_t())
|
||||||
|
: m_node(node)
|
||||||
|
{}
|
||||||
|
inline iterator(const iterator &it)
|
||||||
|
: m_node(it.m_node)
|
||||||
|
{}
|
||||||
|
inline iterator operator++()
|
||||||
|
{
|
||||||
|
// Prefix increment
|
||||||
|
m_node = m_node->m_next_sibling;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
inline iterator operator++(int)
|
||||||
|
{
|
||||||
|
// Postfix increment
|
||||||
|
iterator current(*this);
|
||||||
|
m_node = m_node->m_next_sibling;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
inline Node &operator*() const
|
||||||
|
{
|
||||||
|
return *m_node;
|
||||||
|
}
|
||||||
|
inline Ptr_t operator->() const
|
||||||
|
{
|
||||||
|
return m_node;
|
||||||
|
}
|
||||||
|
inline bool operator==(const iterator &rhs) const
|
||||||
|
{
|
||||||
|
return m_node == rhs.m_node;
|
||||||
|
}
|
||||||
|
inline bool operator!=(const iterator &rhs) const
|
||||||
|
{
|
||||||
|
return m_node != rhs.m_node;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class const_iterator: public std::iterator<std::forward_iterator_tag, const Node>
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
ConstPtr_t m_node;
|
||||||
|
public:
|
||||||
|
inline const_iterator(ConstPtr_t node = ConstPtr_t())
|
||||||
|
: m_node(node)
|
||||||
|
{}
|
||||||
|
inline const_iterator(Ptr_t node = Ptr_t())
|
||||||
|
: m_node(std::const_pointer_cast<const Node>(node))
|
||||||
|
{}
|
||||||
|
inline const_iterator(const const_iterator &it)
|
||||||
|
: m_node(it.m_node)
|
||||||
|
{}
|
||||||
|
inline const_iterator(const Node::iterator &it)
|
||||||
|
: m_node(it.m_node)
|
||||||
|
{}
|
||||||
|
inline const_iterator operator++()
|
||||||
|
{
|
||||||
|
// Prefix increment
|
||||||
|
m_node = m_node->m_next_sibling;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
inline const_iterator operator++(int)
|
||||||
|
{
|
||||||
|
// Postfix increment
|
||||||
|
iterator current(*this);
|
||||||
|
m_node = m_node->m_next_sibling;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
inline const Node &operator*() const
|
||||||
|
{
|
||||||
|
return *m_node;
|
||||||
|
}
|
||||||
|
inline ConstPtr_t operator->() const
|
||||||
|
{
|
||||||
|
return m_node;
|
||||||
|
}
|
||||||
|
inline bool operator==(const const_iterator &rhs) const
|
||||||
|
{
|
||||||
|
return m_node == rhs.m_node;
|
||||||
|
}
|
||||||
|
inline bool operator!=(const const_iterator &rhs) const
|
||||||
|
{
|
||||||
|
return m_node != rhs.m_node;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline iterator begin()
|
||||||
|
{
|
||||||
|
return iterator(m_first_child);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline iterator end()
|
||||||
|
{
|
||||||
|
return iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const_iterator begin() const
|
||||||
|
{
|
||||||
|
return iterator(m_first_child);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const_iterator end() const
|
||||||
|
{
|
||||||
|
return iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
const inline std::string &Key() const
|
||||||
|
{
|
||||||
|
return m_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
const inline std::string &ValueWithDefault(const std::string &default_value) const
|
||||||
|
{
|
||||||
|
if (this == &s_empty_node)
|
||||||
|
return default_value;
|
||||||
|
return m_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const inline std::string &Value() const
|
||||||
|
{
|
||||||
|
//TODO: if empty node, throw exception?
|
||||||
|
return m_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void SetValue(const std::string &value)
|
||||||
|
{
|
||||||
|
if (this != &s_empty_node) // not allowed to modify empty node
|
||||||
|
m_value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool Empty() const
|
||||||
|
{
|
||||||
|
return m_key.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool IsLeaf() const
|
||||||
|
{
|
||||||
|
return m_children.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool HasChildren() const
|
||||||
|
{
|
||||||
|
return !IsLeaf();
|
||||||
|
}
|
||||||
|
|
||||||
|
Node &Get(const std::string &path);
|
||||||
|
const Node &operator[](const std::string &path) const;
|
||||||
|
const Node &Get(const std::string &path) const;
|
||||||
|
void Print(size_t indent_level = 0) const;
|
||||||
|
Node &Create(const std::string &key);
|
||||||
|
Node &Create(const std::string &key, const std::string &value);
|
||||||
|
Node(const std::string &key);
|
||||||
|
Node(const std::string &key, const std::string &value);
|
||||||
|
Node(const Node &that);
|
||||||
|
~Node();
|
||||||
|
};
|
||||||
|
|
||||||
|
Node::Ptr_t CreateEmpty();
|
||||||
|
Node::Ptr_t FromINIFile(const std::string &filename);
|
||||||
|
Node::Ptr_t MergeINISections(const Node &x, const Node &y);
|
||||||
|
void WriteINIFile(const std::string &filename, const Node &config, const std::string &header_comment);
|
||||||
|
} // Config
|
||||||
|
} // Util
|
||||||
|
|
||||||
|
#endif // INCLUDED_UTIL_CONFIG_H
|
61
Src/Util/Test_Config.cpp
Normal file
61
Src/Util/Test_Config.cpp
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
#include "Util/NewConfig.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Creates a config tree corresponding to the following:
|
||||||
|
*
|
||||||
|
* <game>
|
||||||
|
* <name>scud</name>
|
||||||
|
* <roms>
|
||||||
|
* <crom>
|
||||||
|
* <name>foo.bin</name>
|
||||||
|
* <offset>0</offset>
|
||||||
|
* </crom>
|
||||||
|
* <crom>
|
||||||
|
* <name>bar.bin</name>
|
||||||
|
* <offset>2</offset>
|
||||||
|
* </crom>
|
||||||
|
* </roms>
|
||||||
|
* </game>
|
||||||
|
*/
|
||||||
|
Util::Config::Node::Ptr_t root = std::make_shared<Util::Config::Node>("global");
|
||||||
|
auto &game = root->Create("game");
|
||||||
|
game.Create("name", "scud");
|
||||||
|
auto &roms = game.Create("roms");
|
||||||
|
auto &crom1 = roms.Create("crom");
|
||||||
|
crom1.Create("name", "foo.bin");
|
||||||
|
crom1.Create("offset", "0");
|
||||||
|
auto &crom2 = roms.Create("crom");
|
||||||
|
crom2.Create("name", "bar.bin");
|
||||||
|
crom2.Create("offset", "2");
|
||||||
|
|
||||||
|
const char *expected_output =
|
||||||
|
"global children={ game } \n"
|
||||||
|
" game children={ name roms } \n"
|
||||||
|
" name=scud children={ } \n"
|
||||||
|
" roms children={ crom } \n"
|
||||||
|
" crom children={ name offset } \n"
|
||||||
|
" name=foo.bin children={ } \n"
|
||||||
|
" offset=0 children={ } \n"
|
||||||
|
" crom children={ name offset } \n"
|
||||||
|
" name=bar.bin children={ } \n"
|
||||||
|
" offset=2 children={ } \n";
|
||||||
|
std::cout << "Expected output:" << std::endl << std::endl << expected_output << std::endl;
|
||||||
|
std::cout << "Actual config tree:" << std::endl << std::endl;
|
||||||
|
root->Print();
|
||||||
|
std::cout << std::endl;
|
||||||
|
|
||||||
|
// Expect second crom: bar.bin
|
||||||
|
auto &global = *root;
|
||||||
|
std::cout << "game/roms/crom/name=" << global["game/roms/crom/name"].Value() << " (expected: bar.bin)" << std::endl << std::endl;
|
||||||
|
|
||||||
|
// Make a copy
|
||||||
|
std::cout << "Copy:" << std::endl << std::endl;
|
||||||
|
Util::Config::Node::Ptr_t copy = std::make_shared<Util::Config::Node>(*root);
|
||||||
|
copy->Print();
|
||||||
|
std::cout << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue