This is a follow-up to this question.
Here I’m trying to do something useful with the format specifier. I guess I should have started with width
and precision
but foolishly decided to first try my hand at binary formatting. So here we go…
#include <bitset>
#include <charconv>
#include <exception>
#include <iostream>
#include <sstream>
#include <string>
#include <string_view>
namespace jdt
{
template<typename T>
void toBinary(std::ostream& oss, const T& x)
{
oss << std::bitset<sizeof(T) * 8>(x);
}
void toBinary(std::ostream& oss, const double& x)
{
union
{
double a;
unsigned long long b;
} data;
data.a = x;
toBinary(oss, data.b);
}
void toBinary(std::ostream& oss, const float& x)
{
union
{
float a;
unsigned int b;
} data;
data.a = x;
toBinary(oss, data.b);
}
void toBinary(std::ostream& oss, const std::string& x)
{
for (auto c : x)
toBinary(oss, c);
}
void toBinary(std::ostream& oss, const char* x)
{
for (; *x; x++)
toBinary(oss, *x);
}
template<typename T>
void format_helper(std::ostream& oss, std::string_view fmt, const T& value)
{
if (fmt.find('b') != std::string::npos)
{
toBinary(oss, value);
}
else
{
oss << value;
}
}
template<typename... Targs>
static bool format_one(std::ostream& oss, size_t i, std::string_view fmt, const Targs&... args) {
auto visitor = [&](const auto& arg) {
if (!i--) {
format_helper(oss, fmt, arg);
return true;
}
else {
return false;
}
};
return (visitor(args) || ...);
}
template<typename... Targs>
std::string format(std::string_view str, const Targs&... args)
{
std::ostringstream oss;
std::size_t offset = 0;
std::size_t index = str.find('{');
std::size_t argumentCounter = 0;
while (index != std::string::npos)
{
// check for literal open-braces character
if (str[index + 1] == '{')
{
oss.write(str.data() + offset, index - offset + 1);
offset = index + 2;
}
else
{
// check if the next part is an argument index
std::size_t argindex = argumentCounter++;
std::size_t startOfArg = index;
auto ret = std::from_chars(str.data() + index + 1, str.data() + str.size(), argindex);
index = ret.ptr - str.data();
// find closing brace
bool braceError = false;
std::size_t closeBrace = str.find('}', index);
if (closeBrace == std::string::npos)
throw std::runtime_error(format("error in string: {0}, column: {1}: '{{': no matching token found ", std::string(str), startOfArg).c_str());
// check for format specifier
if (str[index] == ':')
index++;
oss.write(str.data() + offset, startOfArg - offset);
if (!format_one(oss, argindex, str.substr(index, closeBrace - index), args...))
throw std::runtime_error(format("error in string: {0}, column: {1}: invalid argument index: {2}", std::string(str), startOfArg, argindex).c_str());
if (argindex < 0)
std::cout << argindex << "\n";
offset = closeBrace + 1;
}
index = str.find('{', offset);
}
if (offset < str.size())
oss << str.substr(offset);
return oss.str();
}
}
int main()
{
int a = 5;
double b = 3.14;
std::string c("hello");
char d = 'Z';
short e = 16634;
float f = 123.456f;
// simple arguments
std::cout << jdt::format("a = {}, b = {}, c = {}\n", a, b, c);
// arguments with indexes
std::cout << jdt::format("a = {2}, b = {1}, c = {0}\n", a, b, c);
// literal open-braces character
std::cout << jdt::format("let object = {{\"a\": {0}, \"b\": {1}, \"c\": \"{2}\"};\n", a, b, c);
// binary formats
std::cout << jdt::format("char: {0} = {0:b}\n", d);
std::cout << jdt::format("short: {0} = {0:b}\n", e);
std::cout << jdt::format("int: {0} = {0:b}\n", a);
std::cout << jdt::format("double: {0} = {0:b}\n", b);
std::cout << jdt::format("float: {0} = {0:b}\n", f);
std::cout << jdt::format("std::string: {0} = {0:b}\n", c);
std::cout << jdt::format("char*: {0} = {0:b}\n", "ABCDEFG");
// too few arguments
std::cout << jdt::format("a = {0}, b = {1}, c = {2}\n", a, b);
// invalid argument indexes
std::cout << jdt::format("a = {0}, b = {10}, c = {2}\n", a, b, c);
// too many arguments
std::cout << jdt::format("a = {2}, b = {1}, c = {0}\n", a, b, c, 3, 4, 5);
// missing closing brace }
std::cout << jdt::format("a = {2}, b = {1}, c = {0\n", a, b, c);
}