{fmt}和ctre

{fmt}是C++20的新的格式化库,用来取代printf和iostream。ctre是捷克女程序员hana开发的编译时正则表达式库,用来取代std::regex。

fmt和ctre焦不离孟。在实现自定义的format时,需要解析format specifacation,这时用正则解析是非常方便的。

德国女程序员在meeting c++上做了 socializing with fmt 的精彩演讲。hana曾在cppcon c++now上多次详细解释ctre原理和性能等。

下文我们使用ctre 重新实现了socializing with fmt中format某种颜色的例子。可以看到我们parse时使用ctre挖取了format specifacation的四个子串,format函数利用parse得到的信息做定制化format。

/* extended specifacation language
extendedspec ::= [optinalExtension][formatspec ]
optionalExtension ::= "{" alternate  [delimiter] "}"
delimiter    ::= a single character other than '{', '}' or '\0'
alternate    ::= ":" | "#"
*/
//
#include <fmt/format.h>
#include <fmt/color.h>
#include <iostream>
#include <vector>
#include <ctre.hpp>
#include <optional>
#include <iostream>
#include <tuple>
static constexpr auto pattern = ctll::fixed_string{ "^(\\{([:#])([^\\{\\}\\0])?\\})?[^\\}]*(.)\\}.*$" };
namespace fmt
{
  template<class Char>
  struct formatter<rgb, Char> :formatter<uint8_t, Char>
  {
    using base = formatter<uint8_t, Char>;
    template<typename FormatContext>
    auto format(const rgb& color, FormatContext& ctx)
    {
      auto oit = ctx.out();
      if (isHex_)
      {
        if (alternate_ == '#')
        {
          oit = '#';
        }
      }
      else {
        if (alternate_ == ':')
        {
          oit = '{';
        }

      }

      oit = base::format(color.r, ctx);
      if (delimeter_)
      {
        oit = delimeter_;
      }
      oit = base::format(color.g, ctx);
      if (delimeter_)
      {
        oit = delimeter_;
      }
      oit = base::format(color.b, ctx);
      if ((!isHex_) && (alternate_ == ':'))
      {
        oit = '}';
      }
      return oit;
    }
    template<typename ParseContext>
    auto parse(ParseContext& ctx)
    {
      if (auto op = extract_spec(std::string_view(ctx.begin(), ctx.end() - ctx.begin())))

      {
        auto [length, alternate, delimeter, lastChar] = *op;
        ctx.advance_to(ctx.begin() + length);
        isHex_ = (lastChar == "X" || lastChar == "x");
        alternate_ = alternate[0];
        delimeter_ = delimeter[0];
      }
      auto r = base::parse(ctx);
      return r;
    }
  private:
    char alternate_ = 0;
    char delimeter_ = 0;
    bool isHex_ = false;
    std::optional<std::tuple<int, std::string_view, std::string_view, std::string_view>> extract_spec(std::string_view s) noexcept {
      if (auto m = ctre::match<pattern>(s)) {
        return std::make_tuple(
          m.get<1>().to_view().size(), //optionalExtension length
          m.get<2>().to_view(),//alternate
          m.get<3>().to_view(),//delimeter
          m.get<4>().to_view());//char before next '}', to know spec is Hex('x'|'X') or not.
      }
      else {
        return std::nullopt;
      }
    }
  };
}
int main()
{
  {
    fmt::rgb bg{ 90,90,10 };
    fmt::rgb fg{ 127,0,0 };
    fmt::print(fmt::bg(bg) | fmt::fg(fg), "{:{#-}02X},{:{:,}02}", bg, fg);
    fmt::print("\n");
  }
}
Posted 2020-08-12