Recently I submitted some string formatting and printing code (found here) that I wrote as an small exercise. It was implemented naively using string replacement. This time around I wanted to actually parse the format string. This is the first time I've ever attempted to write code which does any type of parsing. I'm not 100% sure that this code does not contain any critical flaws. In addition to pointing out any such oversights, I would appreciate advice on how this could be modified to make it either faster or more readable.
The functionality is supposed to be similar to the Console.WriteLine
or String.Format
functions of C#.
#include <boost/lexical_cast.hpp>
#include <iostream>
#include <string>
#include <exception>
#include <utility>
#include <cctype>
template <typename T>
std::string to_string(T&& item)
{
return boost::lexical_cast<std::string>(std::forward<T>(item));
}
template <class... Args>
std::string format(const std::string& fmt, Args&&... args)
{
std::string output;
output.reserve(fmt.length() * 2);
std::string arg_strings[] = { to_string(std::forward<Args>(args))... };
bool placeholder_mode = false;
std::string index_string;
for (auto it = fmt.begin(); it != fmt.end(); ++it) {
//check for end of brace enclosed placeholder
if ((*it == '}') && (placeholder_mode == true)) {
if ((index_string.length() == 0)) {
throw std::invalid_argument("Invalid format string.");
}
//check if index_string contains a leading zero
if ((index_string.length() > 1) && (index_string[0] == '0')) {
throw std::invalid_argument("Invalid format string.");
}
int index = std::stoi(index_string);
if (index > (sizeof...(args) - 1)) {
throw std::out_of_range(
"Index (zero based) must be greater than or equal "
"to zero and less than the size of the argument list."
);
}
output += arg_strings[index];
index_string.clear();
placeholder_mode = false;
continue;
}
//escape double closing brace as single
if ((*it == '}') && (*(it+1) == '}')) {
it++;
output.push_back('}');
continue;
}
//check for unescaped closing brace
//which doesn't close a placeholder
if ((*it == '}') && (placeholder_mode == false)) {
throw std::invalid_argument("Invalid format string.");
}
//escape double opening brace as single
if ((*it == '{') && (*(it+1) == '{')) {
it++;
output.push_back('{');
continue;
}
//check for begining of brace enclosed placeholder
if ((*it == '{') && (placeholder_mode == false)) {
placeholder_mode = true;
continue;
}
if(placeholder_mode == true) {
//check for non-digit characters
if (isdigit(*it) == false) {
throw std::invalid_argument("Invalid format string.");
}
index_string.push_back(*it);
}
else {
output.push_back(*it);
}
}
if (placeholder_mode == true) {
//catch any other placeholders that were left open
throw std::invalid_argument("Invalid format string.");
}
return output;
}
template <class... Args>
void print(const std::string& fmt, Args&&... args)
{
std::cout << format(fmt, std::forward<Args>(args)...);
}
int main()
{
print("Hello, {0}! The answer is {1}.\n", "World", 42);
}
Update: Version 2