Pretty: add the ability to stream out to a generic thing

Instead of always requiring FILE *. This does mean a slight code size
increase, since we now have to pass an extra parameter down the function
stack, plus we're making indirect calls, but it's worth it.

Signed-off-by: Thiago Macieira <thiago.macieira@intel.com>
diff --git a/src/cbor.h b/src/cbor.h
index e55ed1f..6a09a67 100644
--- a/src/cbor.h
+++ b/src/cbor.h
@@ -591,6 +591,13 @@
     CborPrettyDefaultFlags          = CborPrettyIndicateIndetermineLength
 };
 
+typedef CborError (*CborStreamFunction)(void *token, const char *fmt, ...)
+#ifdef __GNUC__
+    __attribute__((__format__(printf, 2, 3)))
+#endif
+;
+
+CBOR_API CborError cbor_value_to_pretty_stream(CborStreamFunction streamFunction, void *token, CborValue *value, int flags);
 CBOR_API CborError cbor_value_to_pretty_advance_flags(FILE *out, CborValue *value, int flags);
 CBOR_API CborError cbor_value_to_pretty_advance(FILE *out, CborValue *value);
 CBOR_INLINE_API CborError cbor_value_to_pretty(FILE *out, const CborValue *value)
diff --git a/src/cborpretty.c b/src/cborpretty.c
index 4ffe31f..f38d676 100644
--- a/src/cborpretty.c
+++ b/src/cborpretty.c
@@ -36,6 +36,7 @@
 #include <float.h>
 #include <inttypes.h>
 #include <math.h>
+#include <stdarg.h>
 #include <stdio.h>
 #include <string.h>
 
@@ -147,29 +148,42 @@
  * \value CborPrettyDefaultFlags       Default conversion flags.
  */
 
-static void printRecursionLimit(FILE *out)
+static CborError cbor_fprintf(void *out, const char *fmt, ...)
 {
-    fputs("<nesting too deep, recursion stopped>", out);
+    int n;
+
+    va_list list;
+    va_start(list, fmt);
+    n = vfprintf((FILE *)out, fmt, list);
+    va_end(list);
+
+    return n < 0 ? CborErrorIO : CborNoError;
 }
 
-static CborError hexDump(FILE *out, const void *ptr, size_t n)
+static void printRecursionLimit(CborStreamFunction stream, void *out)
+{
+    stream(out, "<nesting too deep, recursion stopped>");
+}
+
+static CborError hexDump(CborStreamFunction stream, void *out, const void *ptr, size_t n)
 {
     const uint8_t *buffer = (const uint8_t *)ptr;
-    while (n--) {
-        int r = fprintf(out, "%02" PRIx8, *buffer++);
-        if (r < 0)
-            return CborErrorIO;
-    }
-    return CborNoError;
+    CborError err = CborNoError;
+    while (n-- && !err)
+        err = stream(out, "%02" PRIx8, *buffer++);
+
+    return err;
 }
 
 /* This function decodes buffer as UTF-8 and prints as escaped UTF-16.
  * On UTF-8 decoding error, it returns CborErrorInvalidUtf8TextString */
-static CborError utf8EscapedDump(FILE *out, const void *ptr, size_t n)
+static CborError utf8EscapedDump(CborStreamFunction stream, void *out, const void *ptr, size_t n)
 {
     const uint8_t *buffer = (const uint8_t *)ptr;
     const uint8_t * const end = buffer + n;
-    while (buffer < end) {
+    CborError err = CborNoError;
+
+    while (buffer < end && !err) {
         uint32_t uc = get_utf8(&buffer, end);
         if (uc == ~0U)
             return CborErrorInvalidUtf8TextString;
@@ -177,8 +191,7 @@
         if (uc < 0x80) {
             /* single-byte UTF-8 */
             if (uc < 0x7f && uc >= 0x20 && uc != '\\' && uc != '"') {
-                if (fprintf(out, "%c", (char)uc) < 0)
-                    return CborErrorIO;
+                err = stream(out, "%c", (char)uc);
                 continue;
             }
 
@@ -206,26 +219,23 @@
             default:
                 goto print_utf16;
             }
-            if (fprintf(out, "\\%c", escaped) < 0)
-                return CborErrorIO;
+            err = stream(out, "\\%c", escaped);
             continue;
         }
 
         /* now print the sequence */
         if (uc > 0xffffU) {
             /* needs surrogate pairs */
-            if (fprintf(out, "\\u%04" PRIX32 "\\u%04" PRIX32,
-                        (uc >> 10) + 0xd7c0,    /* high surrogate */
-                        (uc % 0x0400) + 0xdc00) < 0)
-                return CborErrorIO;
+            err = stream(out, "\\u%04" PRIX32 "\\u%04" PRIX32,
+                         (uc >> 10) + 0xd7c0,    /* high surrogate */
+                         (uc % 0x0400) + 0xdc00);
         } else {
 print_utf16:
             /* no surrogate pair needed */
-            if (fprintf(out, "\\u%04" PRIX32, uc) < 0)
-                return CborErrorIO;
+            err = stream(out, "\\u%04" PRIX32, uc);
         }
     }
-    return CborNoError;
+    return err;
 }
 
 static const char *resolve_indicator(const uint8_t *ptr, const uint8_t *end, int flags)
@@ -278,41 +288,39 @@
     return resolve_indicator(it->ptr, it->parser->end, flags);
 }
 
-static CborError value_to_pretty(FILE *out, CborValue *it, int flags, int recursionsLeft);
-static CborError container_to_pretty(FILE *out, CborValue *it, CborType containerType,
+static CborError value_to_pretty(CborStreamFunction stream, void *out, CborValue *it, int flags, int recursionsLeft);
+static CborError container_to_pretty(CborStreamFunction stream, void *out, CborValue *it, CborType containerType,
                                      int flags, int recursionsLeft)
 {
     if (!recursionsLeft) {
-        printRecursionLimit(out);
+        printRecursionLimit(stream, out);
         return CborNoError; /* do allow the dumping to continue */
     }
 
     const char *comma = "";
-    while (!cbor_value_at_end(it)) {
-        if (fprintf(out, "%s", comma) < 0)
-            return CborErrorIO;
+    CborError err = CborNoError;
+    while (!cbor_value_at_end(it) && !err) {
+        err = stream(out, "%s", comma);
         comma = ", ";
 
-        CborError err = value_to_pretty(out, it, flags, recursionsLeft);
-        if (err)
-            return err;
+        if (!err)
+            err = value_to_pretty(stream, out, it, flags, recursionsLeft);
 
         if (containerType == CborArrayType)
             continue;
 
         /* map: that was the key, so get the value */
-        if (fprintf(out, ": ") < 0)
-            return CborErrorIO;
-        err = value_to_pretty(out, it, flags, recursionsLeft);
-        if (err)
-            return err;
+        if (!err)
+            err = stream(out, ": ");
+        if (!err)
+            err = value_to_pretty(stream, out, it, flags, recursionsLeft);
     }
-    return CborNoError;
+    return err;
 }
 
-static CborError value_to_pretty(FILE *out, CborValue *it, int flags, int recursionsLeft)
+static CborError value_to_pretty(CborStreamFunction stream, void *out, CborValue *it, int flags, int recursionsLeft)
 {
-    CborError err;
+    CborError err = CborNoError;
     CborType type = cbor_value_get_type(it);
     switch (type) {
     case CborArrayType:
@@ -322,15 +330,16 @@
         const char *indicator = get_indicator(it, flags);
         const char *space = *indicator ? " " : indicator;
 
-        if (fprintf(out, "%c%s%s", type == CborArrayType ? '[' : '{', indicator, space) < 0)
-            return CborErrorIO;
+        err = stream(out, "%c%s%s", type == CborArrayType ? '[' : '{', indicator, space);
+        if (err)
+            return err;
 
         err = cbor_value_enter_container(it, &recursed);
         if (err) {
             it->ptr = recursed.ptr;
             return err;       /* parse error */
         }
-        err = container_to_pretty(out, &recursed, type, flags, recursionsLeft - 1);
+        err = container_to_pretty(stream, out, &recursed, type, flags, recursionsLeft - 1);
         if (err) {
             it->ptr = recursed.ptr;
             return err;       /* parse error */
@@ -339,9 +348,7 @@
         if (err)
             return err;       /* parse error */
 
-        if (fprintf(out, type == CborArrayType ? "]" : "}") < 0)
-            return CborErrorIO;
-        return CborNoError;
+        return stream(out, type == CborArrayType ? "]" : "}");
     }
 
     case CborIntegerType: {
@@ -349,24 +356,21 @@
         cbor_value_get_raw_integer(it, &val);    /* can't fail */
 
         if (cbor_value_is_unsigned_integer(it)) {
-            if (fprintf(out, "%" PRIu64, val) < 0)
-                return CborErrorIO;
+            err = stream(out, "%" PRIu64, val);
         } else {
             /* CBOR stores the negative number X as -1 - X
              * (that is, -1 is stored as 0, -2 as 1 and so forth) */
             if (++val) {                /* unsigned overflow may happen */
-                if (fprintf(out, "-%" PRIu64, val) < 0)
-                    return CborErrorIO;
+                err = stream(out, "-%" PRIu64, val);
             } else {
                 /* overflown
                  *   0xffff`ffff`ffff`ffff + 1 =
                  * 0x1`0000`0000`0000`0000 = 18446744073709551616 (2^64) */
-                if (fprintf(out, "-18446744073709551616") < 0)
-                    return CborErrorIO;
+                err = stream(out, "-18446744073709551616");
             }
         }
-        if (fprintf(out, "%s", get_indicator(it, flags)) < 0)
-            return CborErrorIO;
+        if (!err)
+            err = stream(out, "%s", get_indicator(it, flags));
         break;
     }
 
@@ -386,90 +390,79 @@
         }
 
         if (showingFragments) {
-            if (fputs("(_ ", out) < 0)
-                return CborErrorIO;
-            err = _cbor_value_prepare_string_iteration(it);
-            if (err)
-                return err;
+            err = stream(out, "(_ ");
+            if (!err)
+                err = _cbor_value_prepare_string_iteration(it);
         } else {
-            if (fputs(open, out) < 0)
-                return CborErrorIO;
+            err = stream(out, "%s", open);
         }
 
-        while (1) {
+        while (!err) {
             if (showingFragments || indicator == NULL) {
                 /* any iteration, except the second for a non-chunked string */
                 indicator = resolve_indicator(it->ptr, it->parser->end, flags);
             }
 
             err = _cbor_value_get_string_chunk(it, &ptr, &n, it);
-            if (err)
-                return err;
             if (!ptr)
                 break;
 
-            if (showingFragments && fprintf(out, "%s%s", separator, open) < 0)
-                return CborErrorIO;
-            err = (type == CborByteStringType ? hexDump(out, ptr, n) : utf8EscapedDump(out, ptr, n));
-            if (err)
-                return err;
-            if (showingFragments) {
-                if (fprintf(out, "%c%s", close, indicator) < 0)
-                    return CborErrorIO;
+            if (!err && showingFragments)
+                err = stream(out, "%s%s", separator, open);
+            if (!err)
+                err = (type == CborByteStringType ?
+                           hexDump(stream, out, ptr, n) :
+                           utf8EscapedDump(stream, out, ptr, n));
+            if (!err && showingFragments) {
+                err = stream(out, "%c%s", close, indicator);
                 separator = ", ";
             }
         }
 
-        if (showingFragments && fputc(')', out) < 0)
-            return CborErrorIO;
-        if (!showingFragments && fprintf(out, "%c%s", close, indicator) < 0)
-            return CborErrorIO;
-        return CborNoError;
+        if (!err) {
+            if (showingFragments)
+                err = stream(out, ")");
+            else
+                err = stream(out, "%c%s", close, indicator);
+        }
+        return err;
     }
 
     case CborTagType: {
         CborTag tag;
         cbor_value_get_tag(it, &tag);       /* can't fail */
-        if (fprintf(out, "%" PRIu64 "%s(", tag, get_indicator(it, flags)) < 0)
-            return CborErrorIO;
-        err = cbor_value_advance_fixed(it);
-        if (err)
-            return err;
-        if (recursionsLeft)
-            err = value_to_pretty(out, it, flags, recursionsLeft - 1);
-        else
-            printRecursionLimit(out);
-        if (err)
-            return err;
-        if (fprintf(out, ")") < 0)
-            return CborErrorIO;
-        return CborNoError;
+        err = stream(out, "%" PRIu64 "%s(", tag, get_indicator(it, flags));
+        if (!err)
+            err = cbor_value_advance_fixed(it);
+        if (!err && recursionsLeft)
+            err = value_to_pretty(stream, out, it, flags, recursionsLeft - 1);
+        else if (!err)
+            printRecursionLimit(stream, out);
+        if (!err)
+            err = stream(out, ")");
+        return err;
     }
 
     case CborSimpleType: {
         /* simple types can't fail and can't have overlong encoding */
         uint8_t simple_type;
         cbor_value_get_simple_type(it, &simple_type);
-        if (fprintf(out, "simple(%" PRIu8 ")", simple_type) < 0)
-            return CborErrorIO;
+        err = stream(out, "simple(%" PRIu8 ")", simple_type);
         break;
     }
 
     case CborNullType:
-        if (fprintf(out, "null") < 0)
-            return CborErrorIO;
+        err = stream(out, "null");
         break;
 
     case CborUndefinedType:
-        if (fprintf(out, "undefined") < 0)
-            return CborErrorIO;
+        err = stream(out, "undefined");
         break;
 
     case CborBooleanType: {
         bool val;
         cbor_value_get_boolean(it, &val);       /* can't fail */
-        if (fprintf(out, val ? "true" : "false") < 0)
-            return CborErrorIO;
+        err = stream(out, val ? "true" : "false");
         break;
     }
 
@@ -504,27 +497,51 @@
         if (ival == fabs(val)) {
             /* this double value fits in a 64-bit integer, so show it as such
              * (followed by a floating point suffix, to disambiguate) */
-            r = fprintf(out, "%s%" PRIu64 ".%s", val < 0 ? "-" : "", ival, suffix);
+            err = stream(out, "%s%" PRIu64 ".%s", val < 0 ? "-" : "", ival, suffix);
         } else {
             /* this number is definitely not a 64-bit integer */
-            r = fprintf(out, "%." DBL_DECIMAL_DIG_STR "g%s", val, suffix);
+            err = stream(out, "%." DBL_DECIMAL_DIG_STR "g%s", val, suffix);
         }
-        if (r < 0)
-            return CborErrorIO;
         break;
     }
 
     case CborInvalidType:
-        if (fprintf(out, "invalid") < 0)
-            return CborErrorIO;
+        err = stream(out, "invalid");
+        if (err)
+            return err;
         return CborErrorUnknownType;
     }
 
-    err = cbor_value_advance_fixed(it);
+    if (!err)
+        err = cbor_value_advance_fixed(it);
     return err;
 }
 
 /**
+ * Converts the current CBOR type pointed by \a value to its textual
+ * representation and writes it to the stream by calling the \a streamFunction.
+ * If an error occurs, this function returns an error code similar to
+ * CborParsing.
+ *
+ * The textual representation can be controlled by the \a flags parameter (see
+ * CborPrettyFlags for more information).
+ *
+ * If no error ocurred, this function advances \a value to the next element.
+ * Often, concatenating the text representation of multiple elements can be
+ * done by appending a comma to the output stream.
+ *
+ * The \a streamFunction function will be called with the \a token value as the
+ * first parameter and a printf-style format string as the second, with a variable
+ * number of further parameters.
+ *
+ * \sa cbor_value_to_pretty(), cbor_value_to_json_advance()
+ */
+CborError cbor_value_to_pretty_stream(CborStreamFunction streamFunction, void *token, CborValue *value, int flags)
+{
+    return value_to_pretty(streamFunction, token, value, flags, CBOR_PARSER_MAX_RECURSIONS);
+}
+
+/**
  * \fn CborError cbor_value_to_pretty(FILE *out, const CborValue *value)
  *
  * Converts the current CBOR type pointed by \a value to its textual
@@ -543,11 +560,11 @@
  * Often, concatenating the text representation of multiple elements can be
  * done by appending a comma to the output stream.
  *
- * \sa cbor_value_to_pretty(), cbor_value_to_json_advance()
+ * \sa cbor_value_to_pretty(), cbor_value_to_pretty_stream(), cbor_value_to_json_advance()
  */
 CborError cbor_value_to_pretty_advance(FILE *out, CborValue *value)
 {
-    return value_to_pretty(out, value, CborPrettyDefaultFlags, CBOR_PARSER_MAX_RECURSIONS);
+    return value_to_pretty(cbor_fprintf, out, value, CborPrettyDefaultFlags, CBOR_PARSER_MAX_RECURSIONS);
 }
 
 /**
@@ -562,11 +579,11 @@
  * Often, concatenating the text representation of multiple elements can be
  * done by appending a comma to the output stream.
  *
- * \sa cbor_value_to_pretty(), cbor_value_to_json_advance()
+ * \sa cbor_value_to_pretty_stream(), cbor_value_to_pretty(), cbor_value_to_json_advance()
  */
 CborError cbor_value_to_pretty_advance_flags(FILE *out, CborValue *value, int flags)
 {
-    return value_to_pretty(out, value, flags, CBOR_PARSER_MAX_RECURSIONS);
+    return value_to_pretty(cbor_fprintf, out, value, flags, CBOR_PARSER_MAX_RECURSIONS);
 }
 
 /** @} */
diff --git a/tests/parser/tst_parser.cpp b/tests/parser/tst_parser.cpp
index b4dc679..28705c1 100644
--- a/tests/parser/tst_parser.cpp
+++ b/tests/parser/tst_parser.cpp
@@ -25,12 +25,8 @@
 #define _XOPEN_SOURCE 700
 #include <QtTest>
 #include "cbor.h"
-#include <locale.h>
 #include <stdio.h>
-
-#ifndef Q_CC_MSVC
-extern "C" FILE *open_memstream(char **bufptr, size_t *sizeptr);
-#endif
+#include <stdarg.h>
 
 Q_DECLARE_METATYPE(CborError)
 namespace QTest {
@@ -103,40 +99,23 @@
     void recursionLimit();
 };
 
+static CborError qstring_printf(void *out, const char *fmt, ...)
+{
+    auto str = static_cast<QString *>(out);
+    va_list va;
+    va_start(va, fmt);
+    *str += QString::vasprintf(fmt, va);
+    va_end(va);
+    return CborNoError;
+};
+
 CborError parseOne(CborValue *it, QString *parsed)
 {
-    CborError err;
-    char *buffer;
-    size_t size;
-
     int flags = CborPrettyShowStringFragments | CborPrettyIndicateIndetermineLength |
                 CborPrettyIndicateOverlongNumbers;
 
-    setlocale(LC_ALL, "C");
-#ifdef Q_CC_MSVC
-    // no open_memstream, so use a temporary file
-    QTemporaryFile tmp("./output.XXXXXX.txt");
-    tmp.open();
-
-    FILE *f = fopen(QFile::encodeName(tmp.fileName()), "w+");
-    if (!f)
-        return CborErrorIO;
-    err = cbor_value_to_pretty_advance_flags(f, it, flags);
-    size = ftell(f);
-    rewind(f);
-
-    buffer = static_cast<char *>(malloc(size));
-    size = fread(buffer, 1, size, f);
-    fclose(f);
-#else
-    FILE *f = open_memstream(&buffer, &size);
-    err = cbor_value_to_pretty_advance_flags(f, it, flags);
-    fclose(f);
-#endif
-
-    *parsed = QString::fromLatin1(buffer, int(size));
-    free(buffer);
-    return err;
+    parsed->clear();
+    return cbor_value_to_pretty_stream(qstring_printf, parsed, it, flags);
 }
 
 CborError parseOneChunk(CborValue *it, QString *parsed)
@@ -331,13 +310,7 @@
     QTest::newRow("2.f^64") << raw("\xfa\x5f\x80\0\0") << "1.8446744073709552e+19f";
     QTest::newRow("2.^64") << raw("\xfb\x43\xf0\0\0\0\0\0\0") << "1.8446744073709552e+19";
 
-    QTest::newRow("nan_f16") << raw("\xf9\x7e\x00")
-#ifdef Q_CC_MSVC
-                             << "-nan(ind)"
-#else
-                             << "nan"
-#endif
-                                ;
+    QTest::newRow("nan_f16") << raw("\xf9\x7e\x00") << "nan";
     QTest::newRow("nan_f") << raw("\xfa\x7f\xc0\0\0") << "nan";
     QTest::newRow("nan") << raw("\xfb\x7f\xf8\0\0\0\0\0\0") << "nan";
     QTest::newRow("-inf_f16") << raw("\xf9\xfc\x00") << "-inf";