This is my second attempt (version 1) at writing a string formatting utility which uses parsing. This time I more explicitly designed it as a finite state machine. I have very little experience with writing FSMs. As before, 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.
#include <boost/lexical_cast.hpp>
#include <iostream>
#include <string>
#include <stdexcept>
#include <utility>
#include <cctype>
using std::to_string;
template <class 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 arg_strs[] = { to_string(std::forward<Args>(args))... };
std::string output, index;
output.reserve(fmt.length() * 2);
enum { COPYING, OPEN_BRACE, READ_INDEX, CLOSE_BRACE } state = COPYING;
for (const char c : fmt)
{
switch (state)
{
case COPYING:
if (c == '{') state = OPEN_BRACE;
else if (c == '}') state = CLOSE_BRACE;
else output += c;
break;
case OPEN_BRACE:
if (isdigit(c)) {
index += c;
state = READ_INDEX;
}
else if (c == '{') {
output += '{';
state = COPYING;
}
else goto fail;
break;
case READ_INDEX:
if (isdigit(c)) index += c;
else if (c == '}') {
size_t i = std::stoi(index);
if (i >= sizeof...(args)) {
throw std::out_of_range(
"argument index is out of range in format");
}
output += arg_strs[i];
index.clear();
state = COPYING;
}
else goto fail;
break;
case CLOSE_BRACE:
if (c == '}') {
output += '}';
state = COPYING;
}
else goto fail;
break;
}
}
if (state != COPYING) goto fail;
return output;
fail:
throw std::invalid_argument("invalid format string");
}
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}.", "World", 42);
}
Here is a diagram of the FSM: