Edward designed this class as a poor man's named property initializer using syntax similar to C99's designated initializers if they worked in a C++ constructor call:
Person me(
.FirstName="Radical",
.LastName="Edward",
.Age=23
);
Edward designed this class as a poor man's named property initializer using syntax similar to C99's designated initializers if they worked in a C++ constructor call:
Person me(
.FirstName="Radical",
.LastName="Edward",
.Age=23
);
#include <iostream>
#include <string>
#include <sstream>
#include <typeinfo>
#include <map>
namespace TextUtil {
/// Manages property/value pairs given as macros.
class MacroCollection {
typedef std::map<std::string, std::string> ParsedMacroList;
static const char MacroBegin = '.'; // Marks the beginning of a macro
static const char MacroEnd = ';'; // Marks the end of a macro
static const char MacroSep = '='; // Separates the parts of a macro
const std::string& _macroList; // The unextracted list of macros
ParsedMacroList _macros; // The extracted list of macros
public:
MacroCollection(const std::string& macroList);
template <typename T> T Extract(const std::string& property);
private:
void ExtractMacros();
void ParseMacro(const std::string& macroText);
};
/// Create and initialize a MacroCollection object.
/// @param macroList A collection of macros represented as a string
/// @throws rumtime_error One or more macros could not be extracted
MacroCollection::MacroCollection(const std::string& macroList)
: _macroList(macroList)
{
ExtractMacros();
}
/// Locate the value for a given property.
/// @param property The property name to search for
/// @return The value as a string, without any translation logic
/// @throws invalid_argument The property was not found
template <>
std::string MacroCollection::Extract(const std::string &property)
{
ParsedMacroList::const_iterator macro = _macros.find(property);
if (macro == _macros.end()) {
std::ostringstream oss;
// Build an informative error message
oss << "Unexpected property [\"" + property +
"\"] for this type. Expected one of: ";
// Include a comma separated list of expected properties
for (macro = _macros.begin(); macro != _macros.end(); ++macro) {
std::string prefix = (macro != _macros.begin()) ? ", " : "(";
oss << prefix << "\"" + macro->first + "\"";
}
oss << ")";
throw std::invalid_argument(oss.str());
}
return macro->second;
}
/// Locate the value for a given property.
/// @param property The property name to search for
/// @return The value after translation into a specified type
/// @throws invalid_argument The property was not found
/// @throws invalid_argument The value could not be translated
template <typename T>
T MacroCollection::Extract(const std::string& property)
{
std::string value = Extract<std::string>(property);
std::istringstream iss(value);
T result;
// Try to translate the string value as type T
if (!(iss >> result)) {
throw std::invalid_argument(
"Invalid type [" + std::string(typeid(T).name()) +
"] for the macro value \"" + value + "\"");
}
return result;
}
/// Parse a list of macros represented as a string.
/// Given a string, extract individual macros for storage
/// @throws runtime_error The macro was not formatted correctly
void MacroCollection::ExtractMacros()
{
// The starting index of a macro
std::string::size_type begin = 0;
// The number of macros that were extracted
int extractedMacros = 0;
// Try to extract a macro based on the initiator character
while ((begin = _macroList.find(MacroBegin, begin)) != std::string::npos) {
std::string::size_type end = _macroList.find(MacroEnd, begin);
// Verify that the macro is delimited correctly
if (end == std::string::npos) {
throw std::runtime_error(
"Invalid macro format found at \"" + _macroList.substr(begin, end) +
"\": Missing macro terminator character (" + MacroEnd + ")");
}
// Separate the macro from the list for parsing
std::string temp = _macroList.substr(begin, end - begin + 1);
// Verify that the macro represents a key/value pair
if (temp.find(MacroSep) == std::string::npos) {
throw std::runtime_error(
"Invalid macro format found at \"" + temp +
"\": Missing macro separator character (" + MacroSep + ")");
}
#ifdef DEV_DEBUG
std::cout << "Extracted macro: " << temp << '\n';
#endif
ParseMacro(temp);
++extractedMacros;
begin = end;
}
// No extracted macros means an
// initiator character was not found
if (extractedMacros == 0) {
throw std::runtime_error(
"Invalid macro format found at \"" + _macroList +
"\": Missing macro initiator character (" + MacroBegin + ")");
}
}
/// Load the property and value of a macro.
/// Given a validated macro, extract the property and value for storage
/// @param macro The valid macro: {begin}{property}{sep}{value}{end}
/// @throws runtime_error The macro was not formatted properly
/// @throws runtime_error The property was blank or a duplicate
void MacroCollection::ParseMacro(const std::string& macro)
{
std::istringstream iss(macro);
std::string property, value;
// Skip the leading macro initiator character
if (iss.get() != MacroBegin) {
std::runtime_error(
"Invalid macro found at \"" + iss.str() + "\". "
"Unexpected macro initiator character (" + MacroBegin + ")");
}
// Split the macro into component parts for storage
if (!std::getline(iss, property, MacroSep)) {
std::runtime_error(
"Invalid macro found at \"" + iss.str() + "\". "
"Unable to extract the required items");
}
if (!std::getline(iss, value, MacroEnd)) {
std::runtime_error(
"Invalid macro found at \"" + iss.str() + "\". "
"Unable to extract the required items");
}
#ifdef DEV_DEBUG
std::cout << "Extracted property: \"" << property << "\"\n"
<< "Extracted value: \"" << value << "\"\n";
#endif
// Properties correspond to class
// data fields and can't be blank
if (property.length() == 0) {
throw std::runtime_error(
"Invalid macro found at \"" + macro +
"\": Blank properties are not allowed");
}
// Duplicate properties are not allowed to avoid
// confusion about potentially unexpected values
if (_macros.find(property) != _macros.end()) {
throw std::runtime_error(
"Invalid macro found at \"" + macro + "\": A macro for " +
"the " + property + " property has already been extracted");
}
_macros[property] = value;
}
}
//
// Sample usage of MacroCollection
//
class Person {
TextUtil::MacroCollection init;
std::string _firstName;
std::string _lastName;
int _age;
public:
Person(const std::string& macroInitText);
friend std::ostream& operator<<(std::ostream& os, const Person& p);
};
Person::Person(const std::string& macroInitText):
init(macroInitText),
_firstName(init.Extract<std::string>("FirstName")),
_lastName(init.Extract<std::string>("LastName")),
_age(init.Extract<int>("Age"))
{}
std::ostream& operator<<(std::ostream& os, const Person& p)
{
return os << p._lastName << ", " << p._firstName << ". Age " << p._age;
}
int main() try
{
Person me(
".FirstName=Radical;"
".LastName=Edward;"
".Age=23;"
);
std::cout << me << '\n';
} catch ( const std::exception& ex ) {
std::cerr << ex.what() << '\n';
}
We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.