[beast: 11/57] SFI: add StringFormatter for type safe printf-like formatting in C++11



commit f3cdbfb6fb2774355c95e327ce05c5b705499946
Author: Tim Janik <timj gnu org>
Date:   Fri Jul 14 03:05:08 2017 +0200

    SFI: add StringFormatter for type safe printf-like formatting in C++11
    
    Signed-off-by: Tim Janik <timj gnu org>

 sfi/formatter.cc |  509 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 sfi/formatter.hh |  169 ++++++++++++++++++
 2 files changed, 678 insertions(+), 0 deletions(-)
---
diff --git a/sfi/formatter.cc b/sfi/formatter.cc
new file mode 100644
index 0000000..7db75e9
--- /dev/null
+++ b/sfi/formatter.cc
@@ -0,0 +1,509 @@
+// This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
+#include "formatter.hh"
+#include <unistd.h>     // isatty
+#include <cstring>
+
+/** @TODO:
+ * - StringFormatter: support directives: %%n %%S %%ls
+ * - StringFormatter: support directive flags: I
+ */
+
+#ifdef __clang__ // clang++-3.8.0: work around 'variable length array of non-POD element type'
+#define CC_DECLARE_VLA(Type, var, count)        std::vector<Type> var (count)
+#else // sane c++
+#define CC_DECLARE_VLA(Type, var, count)        Type var[count]
+#endif
+
+namespace Bse {
+namespace Lib {
+
+template<class... Args> static std::string
+system_string_printf (const char *format, Args... args)
+{
+  char *cstr = NULL;
+  int ret = asprintf (&cstr, format, args...);
+  if (ret >= 0 && cstr)
+    {
+      std::string result = cstr;
+      free (cstr);
+      return result;
+    }
+  return format;
+}
+
+static bool
+parse_unsigned_integer (const char **stringp, uint64_t *up)
+{ // '0' | [1-9] [0-9]* : <= 18446744073709551615
+  const char *p = *stringp;
+  // zero
+  if (*p == '0' && !(p[1] >= '0' && p[1] <= '9'))
+    {
+      *up = 0;
+      *stringp = p + 1;
+      return true;
+    }
+  // first digit
+  if (!(*p >= '1' && *p <= '9'))
+    return false;
+  uint64_t u = *p - '0';
+  p++;
+  // rest digits
+  while (*p >= '0' && *p <= '9')
+    {
+      const uint64_t last = u;
+      u = u * 10 + (*p - '0');
+      p++;
+      if (u < last) // overflow
+        return false;
+    }
+  *up = u;
+  *stringp = p;
+  return true;
+}
+
+static bool
+parse_positional (const char **stringp, uint64_t *ap)
+{ // [0-9]+ '$'
+  const char *p = *stringp;
+  uint64_t ui64 = 0;
+  if (parse_unsigned_integer (&p, &ui64) && *p == '$')
+    {
+      p++;
+      *ap = ui64;
+      *stringp = p;
+      return true;
+    }
+  return false;
+}
+
+const char*
+StringFormatter::parse_directive (const char **stringp, size_t *indexp, Directive *dirp)
+{ // '%' positional? [-+#0 '']* ([0-9]*|[*]positional?) ([.]([0-9]*|[*]positional?))? [hlLjztqZ]* 
[spmcCdiouXxFfGgEeAa]
+  const char *p = *stringp;
+  size_t index = *indexp;
+  Directive fdir;
+  // '%' directive start
+  if (*p != '%')
+    return "missing '%' at start";
+  p++;
+  // positional argument
+  uint64_t ui64 = -1;
+  if (parse_positional (&p, &ui64))
+    {
+      if (ui64 > 0 && ui64 <= 2147483647)
+        fdir.value_index = ui64;
+      else
+        return "invalid positional specification";
+    }
+  // flags
+  const char *flags = "-+#0 '";
+  while (strchr (flags, *p))
+    switch (*p)
+      {
+      case '-': fdir.adjust_left = true;        goto default_case;
+      case '+': fdir.add_sign = true;           goto default_case;
+      case '#': fdir.alternate_form = true;     goto default_case;
+      case '0': fdir.zero_padding = true;       goto default_case;
+      case ' ': fdir.add_space = true;          goto default_case;
+      case '\'': fdir.locale_grouping = true;   goto default_case;
+      default: default_case:
+        p++;
+        break;
+      }
+  // field width
+  ui64 = 0;
+  if (*p == '*')
+    {
+      p++;
+      if (parse_positional (&p, &ui64))
+        {
+          if (ui64 > 0 && ui64 <= 2147483647)
+            fdir.width_index = ui64;
+          else
+            return "invalid positional specification";
+        }
+      else
+        fdir.width_index = index++;
+      fdir.use_width = true;
+    }
+  else if (parse_unsigned_integer (&p, &ui64))
+    {
+      if (ui64 <= 2147483647)
+        fdir.field_width = ui64;
+      else
+        return "invalid field width specification";
+      fdir.use_width = true;
+    }
+  // precision
+  if (*p == '.')
+    {
+      fdir.use_precision = true;
+      p++;
+    }
+  if (*p == '*')
+    {
+      p++;
+      if (parse_positional (&p, &ui64))
+        {
+          if (ui64 > 0 && ui64 <= 2147483647)
+            fdir.precision_index = ui64;
+          else
+            return "invalid positional specification";
+        }
+      else
+        fdir.precision_index = index++;
+    }
+  else if (parse_unsigned_integer (&p, &ui64))
+    {
+      if (ui64 <= 2147483647)
+        fdir.precision = ui64;
+      else
+        return "invalid precision specification";
+    }
+  // modifiers
+  const char *modifiers = "hlLjztqZ";
+  while (strchr (modifiers, *p))
+    p++;
+  // conversion
+  const char *conversion = "dioucCspmXxEeFfGgAa%";
+  if (!strchr (conversion, *p))
+    return "missing conversion specifier";
+  if (fdir.value_index == 0 && !strchr ("m%", *p))
+    fdir.value_index = index++;
+  fdir.conversion = *p++;
+  if (fdir.conversion == 'C')   // %lc in SUSv2
+    fdir.conversion = 'c';
+  // success
+  *dirp = fdir;
+  *indexp = index;
+  *stringp = p;
+  return NULL; // OK
+}
+
+const StringFormatter::FormatArg&
+StringFormatter::format_arg (size_t nth)
+{
+  if (nth && nth <= nargs_)
+    return fargs_[nth-1];
+  static const FormatArg zero_arg = { { 0, }, 0 };
+  return zero_arg;
+}
+
+StringFormatter::LDouble
+StringFormatter::arg_as_ldouble (size_t nth)
+{
+  const FormatArg &farg = format_arg (nth);
+  switch (farg.kind)
+    {
+    case '1':   return farg.i1;
+    case '2':   return farg.i2;
+    case '4':   return farg.i4;
+    case '6':   return farg.i6;
+    case '8':   return farg.i8;
+    case 'f':   return farg.f;
+    case 'd':   return farg.d;
+    case 'p':   return ULLong (farg.p);
+    case 's':   return ULLong (farg.s);
+    default:    return 0;
+    }
+}
+
+const char*
+StringFormatter::arg_as_chars (size_t nth)
+{
+  if (!(nth && nth <= nargs_))
+    return "";
+  if ((fargs_[nth-1].kind == 's' || fargs_[nth-1].kind == 'p') && fargs_[nth-1].p == NULL)
+    return "(null)";
+  return fargs_[nth-1].kind != 's' ? "" : fargs_[nth-1].s;
+}
+
+void*
+StringFormatter::arg_as_ptr (size_t nth)
+{
+  if (!(nth && nth <= nargs_))
+    return NULL;
+  return fargs_[nth-1].p;
+}
+
+StringFormatter::LLong
+StringFormatter::arg_as_longlong (size_t nth)
+{
+  const FormatArg &farg = format_arg (nth);
+  switch (farg.kind)
+    {
+    case '1':   return farg.i1;
+    case '2':   return farg.i2;
+    case '4':   return farg.i4;
+    case '6':   return farg.i6;
+    case '8':   return farg.i8;
+    case 'f':   return farg.f;
+    case 'd':   return farg.d;
+    case 'p':   return LLong (farg.p);
+    case 's':   return LLong (farg.s);
+    default:    return 0;
+    }
+}
+
+uint32_t
+StringFormatter::arg_as_width (size_t nth)
+{
+  int32_t w = arg_as_longlong (nth);
+  w = std::abs (w);
+  return w < 0 ? std::abs (w + 1) : w; // turn -2147483648 into +2147483647
+}
+
+uint32_t
+StringFormatter::arg_as_precision (size_t nth)
+{
+  const int32_t precision = arg_as_longlong (nth);
+  return std::max (0, precision);
+}
+
+template<class Arg> std::string
+StringFormatter::render_arg (const Directive &dir, const char *modifier, Arg arg)
+{
+  std::string result, format;
+  const int field_width = !dir.use_width || !dir.width_index ? dir.field_width : arg_as_width 
(dir.width_index);
+  const int field_precision = !dir.use_precision || !dir.precision_index ? std::max (uint32_t (0), 
dir.precision) : arg_as_precision (dir.precision_index);
+  // format directive
+  format += '%';
+  if (dir.adjust_left)
+    format += '-';
+  if (dir.add_sign)
+    format += '+';
+  if (dir.add_space)
+    format += ' ';
+  if (dir.zero_padding && !dir.adjust_left&& strchr ("diouXx" "FfGgEeAa", dir.conversion))
+    format += '0';
+  if (dir.alternate_form && strchr ("oXx" "FfGgEeAa", dir.conversion))
+    format += '#';
+  if (dir.locale_grouping && strchr ("idu" "FfGg", dir.conversion))
+    format += '\'';
+  if (dir.use_width)
+    format += '*';
+  if (dir.use_precision && strchr ("sm" "diouXx" "FfGgEeAa", dir.conversion)) // !cp
+    format += ".*";
+  if (modifier)
+    format += modifier;
+  format += dir.conversion;
+  // printf formatting
+  if (dir.use_width && dir.use_precision)
+    return system_string_printf (format.c_str(), field_width, field_precision, arg);
+  else if (dir.use_precision)
+    return system_string_printf (format.c_str(), field_precision, arg);
+  else if (dir.use_width)
+    return system_string_printf (format.c_str(), field_width, arg);
+  else
+    return system_string_printf (format.c_str(), arg);
+}
+
+std::string
+StringFormatter::render_directive (const Directive &dir)
+{
+  switch (dir.conversion)
+    {
+    case 'm':
+      return render_arg (dir, "", int (0)); // dummy arg to silence compiler
+    case 'p':
+      return render_arg (dir, "", arg_as_ptr (dir.value_index));
+    case 's': // precision
+      return render_arg (dir, "", arg_as_chars (dir.value_index));
+    case 'c': case 'd': case 'i': case 'o': case 'u': case 'X': case 'x':
+      switch (format_arg (dir.value_index).kind)
+        {
+        case '1':       return render_arg (dir, "hh", format_arg (dir.value_index).i1);
+        case '2':       return render_arg (dir, "h", format_arg (dir.value_index).i2);
+        case '4':       return render_arg (dir, "", format_arg (dir.value_index).i4);
+        case '6':       return render_arg (dir, "l", format_arg (dir.value_index).i6);
+        case '8':       return render_arg (dir, "ll", format_arg (dir.value_index).i8);
+        default:        return render_arg (dir, "ll", arg_as_longlong (dir.value_index));
+        }
+    case 'f': case 'F': case 'e': case 'E': case 'g': case 'G': case 'a': case 'A':
+      switch (format_arg (dir.value_index).kind)
+        {
+        case 'f':       return render_arg (dir, "", format_arg (dir.value_index).f);
+        case 'd':
+        default:        return render_arg (dir, "L", arg_as_ldouble (dir.value_index));
+        }
+    case '%':
+      return "%";
+    }
+  return std::string ("%") + dir.conversion;
+}
+
+static inline size_t
+upper_directive_count (const char *format)
+{
+  size_t n = 0;
+  for (const char *p = format; *p; p++)
+    if (p[0] == '%')            // count %...
+      {
+        n++;
+        if (p[1] == '%')        // dont count %% twice
+          p++;
+      }
+  return n;
+}
+
+std::string
+StringFormatter::render_format (const size_t last, const char *format)
+{
+  if (last != nargs_)
+    { // should never be reached
+      fputs (__FILE__ ": template argument list unpacking failed\n", stderr);
+      return "";
+    }
+  // allocate enough space to hold all directives possibly contained in format
+  const size_t max_dirs = 1 + upper_directive_count (format);
+  CC_DECLARE_VLA (Directive, fdirs, max_dirs); // Directive fdirs[max_dirs];
+  // parse format into Directive stack
+  size_t nextarg = 1, ndirs = 0;
+  const char *p = format;
+  while (*p)
+    {
+      do
+        {
+          if (p[0] == '%')
+            break;
+          p++;
+        }
+      while (*p);
+      if (*p == 0)
+        break;
+      const size_t start = p - format;
+      const char *err = parse_directive (&p, &nextarg, &fdirs[ndirs]);
+      if (err)
+        return format_error (err, format, ndirs + 1);
+      fdirs[ndirs].start = start;
+      fdirs[ndirs].end = p - format;
+      ndirs++;
+      if (!(ndirs < max_dirs))
+        { // should never be reached
+          fputs (__FILE__ ": invalid result from upper_directive_count()", stderr);
+          return "";
+        }
+    }
+  const size_t argcounter = nextarg - 1;
+  fdirs[ndirs].end = fdirs[ndirs].start = p - format;
+  // check maximum argument reference and argument count
+  size_t argmaxref = argcounter;
+  for (size_t i = 0; i < ndirs; i++)
+    {
+      const Directive &fdir = fdirs[i];
+      argmaxref = std::max (argmaxref, size_t (fdir.value_index));
+      argmaxref = std::max (argmaxref, size_t (fdir.width_index));
+      argmaxref = std::max (argmaxref, size_t (fdir.precision_index));
+    }
+  if (argmaxref > last)
+    return format_error ("too few arguments for format", format, 0);
+  if (argmaxref < last)
+    return format_error ("too many arguments for format", format, 0);
+  // format pieces
+  std::string result;
+  p = format;
+  for (size_t i = 0; i <= ndirs; i++)
+    {
+      const Directive &fdir = fdirs[i];
+      result += std::string (p, fdir.start - (p - format));
+      if (fdir.conversion)
+        {
+          std::string rendered_arg = render_directive (fdir);
+          if (arg_transform_)
+            rendered_arg = arg_transform_ (rendered_arg);
+          result += rendered_arg;
+        }
+      p = format + fdir.end;
+    }
+  return result;
+}
+
+std::string
+StringFormatter::locale_format (const size_t last, const char *format)
+{
+  if (locale_context_ == CURRENT_LOCALE)
+    return render_format (last, format);
+  else
+    {
+      ScopedPosixLocale posix_locale_scope; // pushes POSIX locale for this scope
+      return render_format (last, format);
+    }
+}
+
+std::string
+StringFormatter::format_error (const char *err, const char *format, size_t directive)
+{
+  const char *cyan = "", *cred = "", *cyel = "", *crst = "";
+  if (isatty (fileno (stderr)))
+    {
+      const char *term = getenv ("TERM");
+      if (term && strcmp (term, "dumb") != 0)
+        {
+          cyan = "\033[36m";
+          cred = "\033[31m\033[1m";
+          cyel = "\033[33m";
+          crst = "\033[39m\033[22m";
+        }
+    }
+  if (directive)
+    fprintf (stderr, "%sStringFormatter: %sWARNING:%s%s %s in directive %zu:%s %s\n", cyan, cred, crst, 
cyel, err, directive, crst, format);
+  else
+    fprintf (stderr, "%sStringFormatter: %sWARNING:%s%s %s:%s %s\n", cyan, cred, crst, cyel, err, crst, 
format);
+  return format;
+}
+
+ScopedLocale::ScopedLocale (locale_t scope_locale) :
+  locale_ (NULL)
+{
+  if (!scope_locale)
+    locale_ = uselocale (LC_GLOBAL_LOCALE);     // use process locale
+  else
+    locale_ = uselocale (scope_locale);         // use custom locale
+  if (locale_ == NULL)
+    fprintf (stderr, "%s: WARNING: uselocale() returned NULL\n", __FILE__);
+}
+
+ScopedLocale::~ScopedLocale ()
+{
+  uselocale (locale_);                          // restore locale
+}
+
+#if 0
+ScopedLocale::ScopedLocale (const String &locale_name = "")
+{
+  /* this constructor should:
+   * - uselocale (LC_GLOBAL_LOCALE) if locale_name == "",
+   * - create newlocale from locale_name, use it and later delete it, but:
+   * - freelocale(newlocale()) seems buggy on glibc-2.7 (crashes)
+   */
+}
+#endif
+
+ScopedPosixLocale::ScopedPosixLocale () :
+  ScopedLocale (posix_locale())
+{}
+
+locale_t
+ScopedPosixLocale::posix_locale ()
+{
+  static locale_t cached_posix_locale = [] () {
+    locale_t posix_locale = NULL;
+    if (!posix_locale)
+      posix_locale = newlocale (LC_ALL_MASK, "POSIX.UTF-8", NULL);
+    if (!posix_locale)
+      posix_locale = newlocale (LC_ALL_MASK, "C.UTF-8", NULL);
+    if (!posix_locale)
+      posix_locale = newlocale (LC_ALL_MASK, "POSIX", NULL);
+    if (!posix_locale)
+      posix_locale = newlocale (LC_ALL_MASK, "C", NULL);
+    if (!posix_locale)
+      posix_locale = newlocale (LC_ALL_MASK, NULL, NULL);
+    if (posix_locale == NULL)
+      fprintf (stderr, "%s: WARNING: newlocale() returned NULL\n", __FILE__);
+    return posix_locale;
+  } ();
+  return cached_posix_locale;
+}
+
+} // Lib
+} // Bse
diff --git a/sfi/formatter.hh b/sfi/formatter.hh
new file mode 100644
index 0000000..9b15fad
--- /dev/null
+++ b/sfi/formatter.hh
@@ -0,0 +1,169 @@
+// This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
+#ifndef __BSE_FORMATTER_HH__
+#define __BSE_FORMATTER_HH__
+
+#include <string>
+#include <sstream>
+#include <functional>
+#include <vector>
+
+namespace Bse {
+
+/// Namespace for implementation internals
+namespace Lib {
+
+/// Class to push a specific locale_t for the scope of its lifetime.
+class ScopedLocale {
+  locale_t      locale_;
+  /*copy*/      ScopedLocale (const ScopedLocale&) = delete;
+  ScopedLocale& operator=    (const ScopedLocale&) = delete;
+protected:
+  explicit      ScopedLocale (locale_t scope_locale);
+public:
+  // explicit   ScopedLocale (const String &locale_name = ""); // not supported
+  /*dtor*/     ~ScopedLocale ();
+};
+
+/// Class to push the POSIX/C locale_t (UTF-8) for the scope of its lifetime.
+class ScopedPosixLocale : public ScopedLocale {
+public:
+  explicit        ScopedPosixLocale ();
+  static locale_t posix_locale      (); ///< Retrieve the (UTF-8) POSIX/C locale_t.
+};
+
+// == StringFormatter ==
+
+/** StringFormatter - sprintf() like string formatting for C++.
+ *
+ * See format() for supported flags, modifiers and conversions.
+ * To find source code strings with size modifiers for possible cleanups, use:
+ * egrep "\"([^\"]|\\\")*%[0-9$]*[-+#0 \'I]*[*0-9$]*[.*0-9$]*[hlLqjzt]+[nSspmCcdiouXxFfGgEeAa]"
+ */
+class StringFormatter {
+  typedef long long signed int LLong;
+  typedef long long unsigned int ULLong;
+  typedef long double LDouble;
+  struct FormatArg {
+    union { LDouble d; double f; signed char i1; short i2; int i4; long i6; LLong i8; void *p; const char 
*s; };
+    char kind; // f d i u p s
+  };
+  inline void assign (FormatArg &farg, bool               arg) { farg.kind = '1'; farg.i1 = arg; }
+  inline void assign (FormatArg &farg, char               arg) { farg.kind = '1'; farg.i1 = arg; }
+  inline void assign (FormatArg &farg, signed char        arg) { farg.kind = '1'; farg.i1 = arg; }
+  inline void assign (FormatArg &farg, unsigned char      arg) { farg.kind = '1'; farg.i1 = arg; }
+#if __SIZEOF_WCHAR_T__ == 1
+  inline void assign (FormatArg &farg, wchar_t            arg) { farg.kind = '1'; farg.i1 = arg; }
+#endif
+  inline void assign (FormatArg &farg, short              arg) { farg.kind = '2'; farg.i2 = arg; }
+  inline void assign (FormatArg &farg, unsigned short     arg) { farg.kind = '2'; farg.i2 = arg; }
+#if __SIZEOF_WCHAR_T__ == 2
+  inline void assign (FormatArg &farg, wchar_t            arg) { farg.kind = '2'; farg.i2 = arg; }
+#endif
+  inline void assign (FormatArg &farg, int                arg) { farg.kind = '4'; farg.i4 = arg; }
+  inline void assign (FormatArg &farg, unsigned int       arg) { farg.kind = '4'; farg.i4 = arg; }
+#if __SIZEOF_WCHAR_T__ == 4
+  inline void assign (FormatArg &farg, wchar_t            arg) { farg.kind = '4'; farg.i4 = arg; }
+#endif
+  inline void assign (FormatArg &farg, long               arg) { farg.kind = '6'; farg.i6 = arg; }
+  inline void assign (FormatArg &farg, unsigned long      arg) { farg.kind = '6'; farg.i6 = arg; }
+  inline void assign (FormatArg &farg, long long          arg) { farg.kind = '8'; farg.i8 = arg; }
+  inline void assign (FormatArg &farg, unsigned long long arg) { farg.kind = '8'; farg.i8 = arg; }
+  inline void assign (FormatArg &farg, float              arg) { farg.kind = 'f'; farg.f = arg; }
+  inline void assign (FormatArg &farg, double             arg) { farg.kind = 'f'; farg.f = arg; }
+  inline void assign (FormatArg &farg, long double        arg) { farg.kind = 'd'; farg.d = arg; }
+  inline void assign (FormatArg &farg, char              *arg) { farg.kind = 's'; farg.s = arg; }
+  inline void assign (FormatArg &farg, const char        *arg) { farg.kind = 's'; farg.s = arg; }
+  inline void assign (FormatArg &farg, const std::string &arg) { assign (farg, arg.c_str()); }
+  inline void assign (FormatArg &farg, void              *arg) { farg.kind = 'p'; farg.p = arg; }
+  template<class T> inline void assign (FormatArg &farg, T *const &arg) { assign (farg, (void*) arg); }
+  template<class T> typename std::enable_if<std::is_enum<T>::value, void>  // eliminated via SFINAE
+  ::type      assign (FormatArg &farg, const T           &arg) { farg.kind = '8'; farg.i8 = LLong (arg); }
+  template<class T> typename std::enable_if<std::is_class<T>::value, void> // eliminated via SFINAE
+  ::type      assign (FormatArg &farg, const T           &arg)
+  {
+    std::ostringstream os;
+    os << arg;
+    temporaries_.push_back (os.str());
+    assign (farg, temporaries_[temporaries_.size()-1]);
+  }
+  const FormatArg& format_arg       (size_t nth);
+  uint32_t         arg_as_width     (size_t nth);
+  uint32_t         arg_as_precision (size_t nth);
+  LLong            arg_as_longlong  (size_t nth);
+  LDouble          arg_as_ldouble   (size_t nth);
+  const char*      arg_as_chars     (size_t nth);
+  void*            arg_as_ptr       (size_t nth);
+  struct Directive {
+    char     conversion;
+    uint32_t adjust_left : 1, add_sign : 1, use_width : 1, use_precision : 1;
+    uint32_t alternate_form : 1, zero_padding : 1, add_space : 1, locale_grouping : 1;
+    uint32_t field_width, precision, start, end, value_index, width_index, precision_index;
+    Directive() :
+      conversion (0), adjust_left (0), add_sign (0), use_width (0), use_precision (0),
+      alternate_form (0), zero_padding (0), add_space (0), locale_grouping (0),
+      field_width (0), precision (0), start (0), end (0), value_index (0), width_index (0), precision_index 
(0)
+    {}
+  };
+  typedef std::function<std::string (const std::string&)> ArgTransform;
+  FormatArg          *const fargs_;
+  const size_t        nargs_;
+  const int           locale_context_;
+  const ArgTransform &arg_transform_;
+  std::vector<std::string> temporaries_;
+  static std::string            format_error     (const char *err, const char *format, size_t directive);
+  static const char*            parse_directive  (const char **stringp, size_t *indexp, Directive *dirp);
+  std::string                   locale_format    (size_t last, const char *format);
+  std::string                   render_format    (size_t last, const char *format);
+  std::string                   render_directive (const Directive &dir);
+  template<class A> std::string render_arg       (const Directive &dir, const char *modifier, A arg);
+  template<size_t N> inline std::string
+  intern_format (const char *format)
+  {
+    return locale_format (N, format);
+  }
+  template<size_t N, class A, class ...Args> inline std::string
+  intern_format (const char *format, const A &arg, const Args &...args)
+  {
+    assign (fargs_[N], arg);
+    return intern_format<N+1> (format, args...);
+  }
+  template<size_t N> inline constexpr
+  StringFormatter (const ArgTransform &arg_transform, size_t nargs, FormatArg (&mem)[N], int lc) :
+    fargs_ (mem), nargs_ (nargs), locale_context_ (lc), arg_transform_ (arg_transform) {}
+public:
+  enum LocaleContext {
+    POSIX_LOCALE,
+    CURRENT_LOCALE,
+  };
+  /** Format a string according to an sprintf() @a format string with @a arguments.
+   * Refer to sprintf() for the format string details, this function is designed to
+   * serve as an sprintf() replacement and mimick its behaviour as close as possible.
+   * Supported format directive features are:
+   * - Formatting flags (sign conversion, padding, alignment), i.e. the flags: [-#0+ ']
+   * - Field width and precision specifications.
+   * - Positional arguments for field width, precision and value.
+   * - Length modifiers are tolerated: i.e. any of [hlLjztqZ].
+   * - The conversion specifiers [spmcCdiouXxFfGgEeAa].
+   *
+   * Additionally, arguments can be transformed after conversion by passing a std::string
+   * conversion function as @a arg_transform. This may e.g. be used for XML character
+   * escaping of the format argument values. <br/>
+   * @NOTE Format errors, e.g. missing arguments will produce a warning on stderr and
+   * return the @a format string unmodified.
+   * @returns A formatted string.
+   */
+  template<LocaleContext LC = POSIX_LOCALE, class ...Args>
+  static __attribute__ ((__format__ (printf, 2, 0), noinline)) std::string
+  format (const ArgTransform &arg_transform, const char *format, const Args &...arguments)
+  {
+    constexpr size_t N = sizeof... (Args);
+    FormatArg mem[N ? N : 1];
+    StringFormatter formatter (arg_transform, N, mem, LC);
+    return formatter.intern_format<0> (format, arguments...);
+  }
+};
+
+} // Lib
+} // Bse
+
+#endif  // __BSE_FORMATTER_HH__


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]