summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am10
-rw-r--r--fmt.c1429
-rw-r--r--fmt.h72
-rw-r--r--test/test_fmt_sprintf.c291
-rw-r--r--test/test_fmt_sscanf.c726
5 files changed, 2528 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am
index 225c4e2..96217e2 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -27,6 +27,8 @@ librbraun_la_SOURCES = \
cpu.h \
error.c \
error.h \
+ fmt.c \
+ fmt.h \
hash.h \
list.c \
list.h \
@@ -48,6 +50,8 @@ librbraun_la_LIBADD = -lrt -lpthread
bin_PROGRAMS = \
test_avltree \
+ test_fmt_sprintf \
+ test_fmt_sscanf \
test_plist \
test_rbtree \
test_rdxtree \
@@ -56,6 +60,12 @@ bin_PROGRAMS = \
test_avltree_SOURCES = test/test_avltree.c
test_avltree_LDADD = librbraun.la
+test_fmt_sprintf_SOURCES = test/test_fmt_sprintf.c
+test_fmt_sprintf_LDADD = librbraun.la
+
+test_fmt_sscanf_SOURCES = test/test_fmt_sscanf.c
+test_fmt_sscanf_LDADD = librbraun.la
+
test_plist_SOURCES = test/test_plist.c
test_plist_LDADD = librbraun.la
diff --git a/fmt.c b/fmt.c
new file mode 100644
index 0000000..e2e15a5
--- /dev/null
+++ b/fmt.c
@@ -0,0 +1,1429 @@
+/*
+ * Copyright (c) 2010-2017 Richard Braun.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ * Upstream site with license notes :
+ * http://git.sceen.net/rbraun/librbraun.git/
+ */
+
+#include <assert.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "error.h"
+#include "fmt.h"
+
+/*
+ * Size for the temporary number buffer. The minimum base is 8 so 3 bits
+ * are consumed per digit. Add one to round up. The conversion algorithm
+ * doesn't use the null byte.
+ */
+#define FMT_MAX_NUM_SIZE (((sizeof(unsigned long long) * CHAR_BIT) / 3) + 1)
+
+/*
+ * Special size for fmt_vsnprintf(), used when the buffer size is unknown.
+ */
+#define FMT_NOLIMIT ((size_t)-1)
+
+/*
+ * Formatting flags.
+ *
+ * FMT_FORMAT_LOWER must be 0x20 as it is OR'd with fmt_digits, eg.
+ * '0': 0x30 | 0x20 => 0x30 ('0')
+ * 'A': 0x41 | 0x20 => 0x61 ('a')
+ */
+#define FMT_FORMAT_ALT_FORM 0x0001 /* "Alternate form" */
+#define FMT_FORMAT_ZERO_PAD 0x0002 /* Zero padding on the left */
+#define FMT_FORMAT_LEFT_JUSTIFY 0x0004 /* Align text on the left */
+#define FMT_FORMAT_BLANK 0x0008 /* Blank space before positive number */
+#define FMT_FORMAT_SIGN 0x0010 /* Always place a sign (either + or -) */
+#define FMT_FORMAT_LOWER 0x0020 /* To lowercase (for %x) */
+#define FMT_FORMAT_CONV_SIGNED 0x0040 /* Format specifies signed conversion */
+#define FMT_FORMAT_DISCARD 0x0080 /* Discard output (scanf) */
+#define FMT_FORMAT_CHECK_WIDTH 0x0100 /* Check field width (scanf) */
+
+enum {
+ FMT_MODIFIER_NONE,
+ FMT_MODIFIER_CHAR,
+ FMT_MODIFIER_SHORT,
+ FMT_MODIFIER_LONG,
+ FMT_MODIFIER_LONGLONG,
+ FMT_MODIFIER_PTR, /* Used only for %p */
+ FMT_MODIFIER_SIZE,
+ FMT_MODIFIER_PTRDIFF,
+};
+
+enum {
+ FMT_SPECIFIER_INVALID,
+ FMT_SPECIFIER_INT,
+ FMT_SPECIFIER_CHAR,
+ FMT_SPECIFIER_STR,
+ FMT_SPECIFIER_NRCHARS,
+ FMT_SPECIFIER_PERCENT,
+};
+
+/*
+ * Note that copies of the original va_list object are made, because va_arg()
+ * may not reliably be used by different callee functions, and despite the
+ * standard explicitely allowing pointers to va_list objects, it's apparently
+ * very difficult for implementations to provide and is best avoided.
+ */
+
+struct fmt_sprintf_state {
+ const char *format;
+ va_list ap;
+ unsigned int flags;
+ int width;
+ int precision;
+ unsigned int modifier;
+ unsigned int specifier;
+ unsigned int base;
+ char *str;
+ char *start;
+ char *end;
+};
+
+struct fmt_sscanf_state {
+ const char *str;
+ const char *start;
+ const char *format;
+ va_list ap;
+ unsigned int flags;
+ int width;
+ int precision;
+ unsigned int modifier;
+ unsigned int specifier;
+ unsigned int base;
+ int nr_convs;
+};
+
+static const char fmt_digits[] = "0123456789ABCDEF";
+
+static char
+fmt_consume(const char **strp)
+{
+ char c;
+
+ c = **strp;
+ (*strp)++;
+ return c;
+}
+
+static void
+fmt_restore(const char **strp)
+{
+ (*strp)--;
+}
+
+static void
+fmt_vsnprintf_produce(char **strp, char *end, char c)
+{
+ if (*strp < end) {
+ **strp = c;
+ }
+
+ (*strp)++;
+}
+
+static bool
+fmt_isdigit(char c)
+{
+ return (c >= '0') && (c <= '9');
+}
+
+static bool
+fmt_isxdigit(char c)
+{
+ return fmt_isdigit(c)
+ || ((c >= 'a') && (c <= 'f'))
+ || ((c >= 'A') && (c <= 'F'));
+}
+
+static void
+fmt_sprintf_state_init(struct fmt_sprintf_state *state,
+ char *str, size_t size,
+ const char *format, va_list ap)
+{
+ state->format = format;
+ va_copy(state->ap, ap);
+ state->str = str;
+ state->start = str;
+
+ if (size == 0) {
+ state->end = NULL;
+ } else if (size == FMT_NOLIMIT) {
+ state->end = (char *)-1;
+ } else {
+ state->end = state->start + size - 1;
+ }
+}
+
+static int
+fmt_sprintf_state_finalize(struct fmt_sprintf_state *state)
+{
+ va_end(state->ap);
+
+ if (state->str < state->end) {
+ *state->str = '\0';
+ } else if (state->end != NULL) {
+ *state->end = '\0';
+ }
+
+ return state->str - state->start;
+}
+
+static char
+fmt_sprintf_state_consume_format(struct fmt_sprintf_state *state)
+{
+ return fmt_consume(&state->format);
+}
+
+static void
+fmt_sprintf_state_restore_format(struct fmt_sprintf_state *state)
+{
+ fmt_restore(&state->format);
+}
+
+static void
+fmt_sprintf_state_consume_flags(struct fmt_sprintf_state *state)
+{
+ bool found;
+ char c;
+
+ found = true;
+ state->flags = 0;
+
+ do {
+ c = fmt_sprintf_state_consume_format(state);
+
+ switch (c) {
+ case '#':
+ state->flags |= FMT_FORMAT_ALT_FORM;
+ break;
+ case '0':
+ state->flags |= FMT_FORMAT_ZERO_PAD;
+ break;
+ case '-':
+ state->flags |= FMT_FORMAT_LEFT_JUSTIFY;
+ break;
+ case ' ':
+ state->flags |= FMT_FORMAT_BLANK;
+ break;
+ case '+':
+ state->flags |= FMT_FORMAT_SIGN;
+ break;
+ default:
+ found = false;
+ break;
+ }
+ } while (found);
+
+ fmt_sprintf_state_restore_format(state);
+}
+
+static void
+fmt_sprintf_state_consume_width(struct fmt_sprintf_state *state)
+{
+ char c;
+
+ c = fmt_sprintf_state_consume_format(state);
+
+ if (fmt_isdigit(c)) {
+ state->width = 0;
+
+ do {
+ state->width = state->width * 10 + (c - '0');
+ c = fmt_sprintf_state_consume_format(state);
+ } while (fmt_isdigit(c));
+
+ fmt_sprintf_state_restore_format(state);
+ } else if (c == '*') {
+ state->width = va_arg(state->ap, int);
+
+ if (state->width < 0) {
+ state->flags |= FMT_FORMAT_LEFT_JUSTIFY;
+ state->width = -state->width;
+ }
+ } else {
+ state->width = 0;
+ fmt_sprintf_state_restore_format(state);
+ }
+}
+
+static void
+fmt_sprintf_state_consume_precision(struct fmt_sprintf_state *state)
+{
+ char c;
+
+ c = fmt_sprintf_state_consume_format(state);
+
+ if (c == '.') {
+ c = fmt_sprintf_state_consume_format(state);
+
+ if (fmt_isdigit(c)) {
+ state->precision = 0;
+
+ do {
+ state->precision = state->precision * 10 + (c - '0');
+ c = fmt_sprintf_state_consume_format(state);
+ } while (fmt_isdigit(c));
+
+ fmt_sprintf_state_restore_format(state);
+ } else if (c == '*') {
+ state->precision = va_arg(state->ap, int);
+
+ if (state->precision < 0) {
+ state->precision = 0;
+ }
+ } else {
+ state->precision = 0;
+ fmt_sprintf_state_restore_format(state);
+ }
+ } else {
+ /* precision is >= 0 only if explicit */
+ state->precision = -1;
+ fmt_sprintf_state_restore_format(state);
+ }
+}
+
+static void
+fmt_sprintf_state_consume_modifier(struct fmt_sprintf_state *state)
+{
+ char c, c2;
+
+ c = fmt_sprintf_state_consume_format(state);
+
+ switch (c) {
+ case 'h':
+ case 'l':
+ c2 = fmt_sprintf_state_consume_format(state);
+
+ if (c == c2) {
+ state->modifier = (c == 'h') ? FMT_MODIFIER_CHAR
+ : FMT_MODIFIER_LONGLONG;
+ } else {
+ state->modifier = (c == 'h') ? FMT_MODIFIER_SHORT
+ : FMT_MODIFIER_LONG;
+ fmt_sprintf_state_restore_format(state);
+ }
+
+ break;
+ case 'z':
+ state->modifier = FMT_MODIFIER_SIZE;
+ case 't':
+ state->modifier = FMT_MODIFIER_PTRDIFF;
+ break;
+ default:
+ state->modifier = FMT_MODIFIER_NONE;
+ fmt_sprintf_state_restore_format(state);
+ break;
+ }
+}
+
+static void
+fmt_sprintf_state_consume_specifier(struct fmt_sprintf_state *state)
+{
+ char c;
+
+ c = fmt_sprintf_state_consume_format(state);
+
+ switch (c) {
+ case 'd':
+ case 'i':
+ state->flags |= FMT_FORMAT_CONV_SIGNED;
+ case 'u':
+ state->base = 10;
+ state->specifier = FMT_SPECIFIER_INT;
+ break;
+ case 'o':
+ state->base = 8;
+ state->specifier = FMT_SPECIFIER_INT;
+ break;
+ case 'p':
+ state->flags |= FMT_FORMAT_ALT_FORM;
+ state->modifier = FMT_MODIFIER_PTR;
+ case 'x':
+ state->flags |= FMT_FORMAT_LOWER;
+ case 'X':
+ state->base = 16;
+ state->specifier = FMT_SPECIFIER_INT;
+ break;
+ case 'c':
+ state->specifier = FMT_SPECIFIER_CHAR;
+ break;
+ case 's':
+ state->specifier = FMT_SPECIFIER_STR;
+ break;
+ case 'n':
+ state->specifier = FMT_SPECIFIER_NRCHARS;
+ break;
+ case '%':
+ state->specifier = FMT_SPECIFIER_PERCENT;
+ break;
+ default:
+ state->specifier = FMT_SPECIFIER_INVALID;
+ fmt_sprintf_state_restore_format(state);
+ break;
+ }
+}
+
+static void
+fmt_sprintf_state_produce_raw_char(struct fmt_sprintf_state *state, char c)
+{
+ fmt_vsnprintf_produce(&state->str, state->end, c);
+}
+
+static int
+fmt_sprintf_state_consume(struct fmt_sprintf_state *state)
+{
+ char c;
+
+ c = fmt_consume(&state->format);
+
+ if (c == '\0') {
+ return ERR_NORES;
+ }
+
+ if (c != '%') {
+ fmt_sprintf_state_produce_raw_char(state, c);
+ return ERR_AGAIN;
+ }
+
+ fmt_sprintf_state_consume_flags(state);
+ fmt_sprintf_state_consume_width(state);
+ fmt_sprintf_state_consume_precision(state);
+ fmt_sprintf_state_consume_modifier(state);
+ fmt_sprintf_state_consume_specifier(state);
+ return 0;
+}
+
+static void
+fmt_sprintf_state_produce_int(struct fmt_sprintf_state *state)
+{
+ char c, sign, tmp[FMT_MAX_NUM_SIZE];
+ unsigned int r, mask, shift;
+ unsigned long long n;
+ int i;
+
+ switch (state->modifier) {
+ case FMT_MODIFIER_CHAR:
+ if (state->flags & FMT_FORMAT_CONV_SIGNED) {
+ n = (signed char)va_arg(state->ap, int);
+ } else {
+ n = (unsigned char)va_arg(state->ap, int);
+ }
+
+ break;
+ case FMT_MODIFIER_SHORT:
+ if (state->flags & FMT_FORMAT_CONV_SIGNED) {
+ n = (short)va_arg(state->ap, int);
+ } else {
+ n = (unsigned short)va_arg(state->ap, int);
+ }
+
+ break;
+ case FMT_MODIFIER_LONG:
+ if (state->flags & FMT_FORMAT_CONV_SIGNED) {
+ n = va_arg(state->ap, long);
+ } else {
+ n = va_arg(state->ap, unsigned long);
+ }
+
+ break;
+ case FMT_MODIFIER_LONGLONG:
+ if (state->flags & FMT_FORMAT_CONV_SIGNED) {
+ n = va_arg(state->ap, long long);
+ } else {
+ n = va_arg(state->ap, unsigned long long);
+ }
+
+ break;
+ case FMT_MODIFIER_PTR:
+ n = (uintptr_t)va_arg(state->ap, void *);
+ break;
+ case FMT_MODIFIER_SIZE:
+ if (state->flags & FMT_FORMAT_CONV_SIGNED) {
+ n = va_arg(state->ap, ssize_t);
+ } else {
+ n = va_arg(state->ap, size_t);
+ }
+
+ break;
+ case FMT_MODIFIER_PTRDIFF:
+ n = va_arg(state->ap, ptrdiff_t);
+ break;
+ default:
+ if (state->flags & FMT_FORMAT_CONV_SIGNED) {
+ n = va_arg(state->ap, int);
+ } else {
+ n = va_arg(state->ap, unsigned int);
+ }
+
+ break;
+ }
+
+ if ((state->flags & FMT_FORMAT_LEFT_JUSTIFY) || (state->precision >= 0)) {
+ state->flags &= ~FMT_FORMAT_ZERO_PAD;
+ }
+
+ sign = '\0';
+
+ if (state->flags & FMT_FORMAT_ALT_FORM) {
+ /* '0' for octal */
+ state->width--;
+
+ /* '0x' or '0X' for hexadecimal */
+ if (state->base == 16) {
+ state->width--;
+ }
+ } else if (state->flags & FMT_FORMAT_CONV_SIGNED) {
+ if ((long long)n < 0) {
+ sign = '-';
+ state->width--;
+ n = -(long long)n;
+ } else if (state->flags & FMT_FORMAT_SIGN) {
+ /* FMT_FORMAT_SIGN must precede FMT_FORMAT_BLANK. */
+ sign = '+';
+ state->width--;
+ } else if (state->flags & FMT_FORMAT_BLANK) {
+ sign = ' ';
+ state->width--;
+ }
+ }
+
+ /* Conversion, in reverse order */
+
+ i = 0;
+
+ if (n == 0) {
+ if (state->precision != 0) {
+ tmp[i++] = '0';
+ }
+ } else if (state->base == 10) {
+ /*
+ * Try to avoid 64 bits operations if the processor doesn't
+ * support them. Note that even when using modulus and
+ * division operators close to each other, the compiler may
+ * forge two functions calls to compute the quotient and the
+ * remainder, whereas processor instructions are generally
+ * correctly used once, giving both results at once, through
+ * plain or reciprocal division.
+ */
+#ifndef __LP64__
+ if (state->modifier == FMT_MODIFIER_LONGLONG) {
+#endif /* __LP64__ */
+ do {
+ r = n % 10;
+ n /= 10;
+ tmp[i++] = fmt_digits[r];
+ } while (n != 0);
+#ifndef __LP64__
+ } else {
+ unsigned long m;
+
+ m = (unsigned long)n;
+
+ do {
+ r = m % 10;
+ m /= 10;
+ tmp[i++] = fmt_digits[r];
+ } while (m != 0);
+ }
+#endif /* __LP64__ */
+ } else {
+ mask = state->base - 1;
+ shift = (state->base == 8) ? 3 : 4;
+
+ do {
+ r = n & mask;
+ n >>= shift;
+ tmp[i++] = fmt_digits[r] | (state->flags & FMT_FORMAT_LOWER);
+ } while (n != 0);
+ }
+
+ if (i > state->precision) {
+ state->precision = i;
+ }
+
+ state->width -= state->precision;
+
+ if (!(state->flags & (FMT_FORMAT_LEFT_JUSTIFY | FMT_FORMAT_ZERO_PAD))) {
+ while (state->width-- > 0) {
+ fmt_sprintf_state_produce_raw_char(state, ' ');
+ }
+ }
+
+ if (state->flags & FMT_FORMAT_ALT_FORM) {
+ fmt_sprintf_state_produce_raw_char(state, '0');
+
+ if (state->base == 16) {
+ c = 'X' | (state->flags & FMT_FORMAT_LOWER);
+ fmt_sprintf_state_produce_raw_char(state, c);
+ }
+ } else if (sign != '\0') {
+ fmt_sprintf_state_produce_raw_char(state, sign);
+ }
+
+ if (!(state->flags & FMT_FORMAT_LEFT_JUSTIFY)) {
+ c = (state->flags & FMT_FORMAT_ZERO_PAD) ? '0' : ' ';
+
+ while (state->width-- > 0) {
+ fmt_sprintf_state_produce_raw_char(state, c);
+ }
+ }
+
+ while (i < state->precision--) {
+ fmt_sprintf_state_produce_raw_char(state, '0');
+ }
+
+ while (i-- > 0) {
+ fmt_sprintf_state_produce_raw_char(state, tmp[i]);
+ }
+
+ while (state->width-- > 0) {
+ fmt_sprintf_state_produce_raw_char(state, ' ');
+ }
+}
+
+static void
+fmt_sprintf_state_produce_char(struct fmt_sprintf_state *state)
+{
+ char c;
+
+ c = va_arg(state->ap, int);
+
+ if (!(state->flags & FMT_FORMAT_LEFT_JUSTIFY)) {
+ while (--state->width > 0) {
+ fmt_sprintf_state_produce_raw_char(state, ' ');
+ }
+ }
+
+ fmt_sprintf_state_produce_raw_char(state, c);
+
+ while (--state->width > 0) {
+ fmt_sprintf_state_produce_raw_char(state, ' ');
+ }
+}
+
+static void
+fmt_sprintf_state_produce_str(struct fmt_sprintf_state *state)
+{
+ int i, len;
+ char *s;
+
+ s = va_arg(state->ap, char *);
+
+ if (s == NULL) {
+ s = "(null)";
+ }
+
+ len = 0;
+
+ for (len = 0; s[len] != '\0'; len++)
+ if (len == state->precision) {
+ break;
+ }
+
+ if (!(state->flags & FMT_FORMAT_LEFT_JUSTIFY)) {
+ while (len < state->width) {
+ state->width--;
+ fmt_sprintf_state_produce_raw_char(state, ' ');
+ }
+ }
+
+ for (i = 0; i < len; i++) {
+ fmt_sprintf_state_produce_raw_char(state, *s);
+ s++;
+ }
+
+ while (len < state->width) {
+ state->width--;
+ fmt_sprintf_state_produce_raw_char(state, ' ');
+ }
+}
+
+static void
+fmt_sprintf_state_produce_nrchars(struct fmt_sprintf_state *state)
+{
+ if (state->modifier == FMT_MODIFIER_CHAR) {
+ signed char *ptr = va_arg(state->ap, signed char *);
+ *ptr = state->str - state->start;
+ } else if (state->modifier == FMT_MODIFIER_SHORT) {
+ short *ptr = va_arg(state->ap, short *);
+ *ptr = state->str - state->start;
+ } else if (state->modifier == FMT_MODIFIER_LONG) {
+ long *ptr = va_arg(state->ap, long *);
+ *ptr = state->str - state->start;
+ } else if (state->modifier == FMT_MODIFIER_LONGLONG) {
+ long long *ptr = va_arg(state->ap, long long *);
+ *ptr = state->str - state->start;
+ } else if (state->modifier == FMT_MODIFIER_SIZE) {
+ ssize_t *ptr = va_arg(state->ap, ssize_t *);
+ *ptr = state->str - state->start;
+ } else if (state->modifier == FMT_MODIFIER_PTRDIFF) {
+ ptrdiff_t *ptr = va_arg(state->ap, ptrdiff_t *);
+ *ptr = state->str - state->start;
+ } else {
+ int *ptr = va_arg(state->ap, int *);
+ *ptr = state->str - state->start;
+ }
+}
+
+static void
+fmt_sprintf_state_produce(struct fmt_sprintf_state *state)
+{
+ switch (state->specifier) {
+ case FMT_SPECIFIER_INT:
+ fmt_sprintf_state_produce_int(state);
+ break;
+ case FMT_SPECIFIER_CHAR:
+ fmt_sprintf_state_produce_char(state);
+ break;
+ case FMT_SPECIFIER_STR:
+ fmt_sprintf_state_produce_str(state);
+ break;
+ case FMT_SPECIFIER_NRCHARS:
+ fmt_sprintf_state_produce_nrchars(state);
+ break;
+ case FMT_SPECIFIER_PERCENT:
+ case FMT_SPECIFIER_INVALID:
+ fmt_sprintf_state_produce_raw_char(state, '%');
+ break;
+ }
+}
+
+int
+fmt_sprintf(char *str, const char *format, ...)
+{
+ va_list ap;
+ int length;
+
+ va_start(ap, format);
+ length = fmt_vsprintf(str, format, ap);
+ va_end(ap);
+
+ return length;
+}
+
+int
+fmt_vsprintf(char *str, const char *format, va_list ap)
+{
+ return fmt_vsnprintf(str, FMT_NOLIMIT, format, ap);
+}
+
+int
+fmt_snprintf(char *str, size_t size, const char *format, ...)
+{
+ va_list ap;
+ int length;
+
+ va_start(ap, format);
+ length = fmt_vsnprintf(str, size, format, ap);
+ va_end(ap);
+
+ return length;
+}
+
+int
+fmt_vsnprintf(char *str, size_t size, const char *format, va_list ap)
+{
+ struct fmt_sprintf_state state;
+ int error;
+
+ fmt_sprintf_state_init(&state, str, size, format, ap);
+
+ for (;;) {
+ error = fmt_sprintf_state_consume(&state);
+
+ if (error == ERR_AGAIN) {
+ continue;
+ } else if (error) {
+ break;
+ }
+
+ fmt_sprintf_state_produce(&state);
+ }
+
+ return fmt_sprintf_state_finalize(&state);
+}
+
+static char
+fmt_atoi(char c)
+{
+ assert(fmt_isxdigit(c));
+
+ if (fmt_isdigit(c)) {
+ return c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ return 10 + (c - 'a');
+ } else {
+ return 10 + (c - 'A');
+ }
+}
+
+static bool
+fmt_isspace(char c)
+{
+ if (c == ' ') {
+ return true;
+ }
+
+ if ((c >= '\t') && (c <= '\f')) {
+ return true;
+ }
+
+ return false;
+}
+
+static void
+fmt_skip(const char **strp)
+{
+ while (fmt_isspace(**strp)) {
+ (*strp)++;
+ }
+}
+
+static void
+fmt_sscanf_state_init(struct fmt_sscanf_state *state, const char *str,
+ const char *format, va_list ap)
+{
+ state->str = str;
+ state->start = str;
+ state->format = format;
+ state->flags = 0;
+ state->width = 0;
+ va_copy(state->ap, ap);
+ state->nr_convs = 0;
+}
+
+static int
+fmt_sscanf_state_finalize(struct fmt_sscanf_state *state)
+{
+ va_end(state->ap);
+ return state->nr_convs;
+}
+
+static void
+fmt_sscanf_state_report_conv(struct fmt_sscanf_state *state)
+{
+ if (state->nr_convs == EOF) {
+ state->nr_convs = 1;
+ return;
+ }
+
+ state->nr_convs++;
+}
+
+static void
+fmt_sscanf_state_report_error(struct fmt_sscanf_state *state)
+{
+ if (state->nr_convs != 0) {
+ return;
+ }
+
+ state->nr_convs = EOF;
+}
+
+static void
+fmt_sscanf_state_skip_space(struct fmt_sscanf_state *state)
+{
+ fmt_skip(&state->str);
+}
+
+static char
+fmt_sscanf_state_consume_string(struct fmt_sscanf_state *state)
+{
+ char c;
+
+ c = fmt_consume(&state->str);
+
+ if (state->flags & FMT_FORMAT_CHECK_WIDTH) {
+ if (state->width == 0) {
+ c = EOF;
+ } else {
+ state->width--;
+ }
+ }
+
+ return c;
+}
+
+static void
+fmt_sscanf_state_restore_string(struct fmt_sscanf_state *state)
+{
+ fmt_restore(&state->str);
+}
+
+static char
+fmt_sscanf_state_consume_format(struct fmt_sscanf_state *state)
+{
+ return fmt_consume(&state->format);
+}
+
+static void
+fmt_sscanf_state_restore_format(struct fmt_sscanf_state *state)
+{
+ fmt_restore(&state->format);
+}
+
+static void
+fmt_sscanf_state_consume_flags(struct fmt_sscanf_state *state)
+{
+ bool found;
+ char c;
+
+ found = true;
+ state->flags = 0;
+
+ do {
+ c = fmt_sscanf_state_consume_format(state);
+
+ switch (c) {
+ case '*':
+ state->flags |= FMT_FORMAT_DISCARD;
+ break;
+ default:
+ found = false;
+ break;
+ }
+ } while (found);
+
+ fmt_sscanf_state_restore_format(state);
+}
+
+static void
+fmt_sscanf_state_consume_width(struct fmt_sscanf_state *state)
+{
+ char c;
+
+ state->width = 0;
+
+ for (;;) {
+ c = fmt_sscanf_state_consume_format(state);
+
+ if (!fmt_isdigit(c)) {
+ break;
+ }
+
+ state->width = state->width * 10 + (c - '0');
+ }
+
+ if (state->width != 0) {
+ state->flags |= FMT_FORMAT_CHECK_WIDTH;
+ }
+
+ fmt_sscanf_state_restore_format(state);
+}
+
+static void
+fmt_sscanf_state_consume_modifier(struct fmt_sscanf_state *state)
+{
+ char c, c2;
+
+ c = fmt_sscanf_state_consume_format(state);
+
+ switch (c) {
+ case 'h':
+ case 'l':
+ c2 = fmt_sscanf_state_consume_format(state);
+
+ if (c == c2) {
+ state->modifier = (c == 'h') ? FMT_MODIFIER_CHAR
+ : FMT_MODIFIER_LONGLONG;
+ } else {
+ state->modifier = (c == 'h') ? FMT_MODIFIER_SHORT
+ : FMT_MODIFIER_LONG;
+ fmt_sscanf_state_restore_format(state);
+ }
+
+ break;
+ case 'z':
+ state->modifier = FMT_MODIFIER_SIZE;
+ break;
+ case 't':
+ state->modifier = FMT_MODIFIER_PTRDIFF;
+ break;
+ default:
+ state->modifier = FMT_MODIFIER_NONE;
+ fmt_sscanf_state_restore_format(state);
+ break;
+ }
+}
+
+static void
+fmt_sscanf_state_consume_specifier(struct fmt_sscanf_state *state)
+{
+ char c;
+
+ c = fmt_sscanf_state_consume_format(state);
+
+ switch (c) {
+ case 'i':
+ state->base = 0;
+ state->flags |= FMT_FORMAT_CONV_SIGNED;
+ state->specifier = FMT_SPECIFIER_INT;
+ break;
+ case 'd':
+ state->flags |= FMT_FORMAT_CONV_SIGNED;
+ case 'u':
+ state->base = 10;
+ state->specifier = FMT_SPECIFIER_INT;
+ break;
+ case 'o':
+ state->base = 8;
+ state->specifier = FMT_SPECIFIER_INT;
+ break;
+ case 'p':
+ state->modifier = FMT_MODIFIER_PTR;
+ case 'x':
+ case 'X':
+ state->base = 16;
+ state->specifier = FMT_SPECIFIER_INT;
+ break;
+ case 'c':
+ state->specifier = FMT_SPECIFIER_CHAR;
+ break;
+ case 's':
+ state->specifier = FMT_SPECIFIER_STR;
+ break;
+ case 'n':
+ state->specifier = FMT_SPECIFIER_NRCHARS;
+ break;
+ case '%':
+ state->specifier = FMT_SPECIFIER_PERCENT;
+ break;
+ default:
+ state->specifier = FMT_SPECIFIER_INVALID;
+ fmt_sscanf_state_restore_format(state);
+ break;
+ }
+}
+
+static int
+fmt_sscanf_state_discard_char(struct fmt_sscanf_state *state, char c)
+{
+ char c2;
+
+ if (fmt_isspace(c)) {
+ fmt_sscanf_state_skip_space(state);
+ return 0;
+ }
+
+ c2 = fmt_sscanf_state_consume_string(state);
+
+ if (c != c2) {
+ if ((c2 == '\0') && (state->nr_convs == 0)) {
+ state->nr_convs = EOF;
+ }
+
+ return ERR_FORMAT;
+ }
+
+ return 0;
+}
+
+static int
+fmt_sscanf_state_consume(struct fmt_sscanf_state *state)
+{
+ int error;
+ char c;
+
+ state->flags = 0;
+
+ c = fmt_consume(&state->format);
+
+ if (c == '\0') {
+ return ERR_NORES;
+ }
+
+ if (c != '%') {
+ error = fmt_sscanf_state_discard_char(state, c);
+
+ if (error) {
+ return error;
+ }
+
+ return ERR_AGAIN;
+ }
+
+ fmt_sscanf_state_consume_flags(state);
+ fmt_sscanf_state_consume_width(state);
+ fmt_sscanf_state_consume_modifier(state);
+ fmt_sscanf_state_consume_specifier(state);
+ return 0;
+}
+
+static int
+fmt_sscanf_state_produce_int(struct fmt_sscanf_state *state)
+{
+ unsigned long long n, m, tmp;
+ char c, buf[FMT_MAX_NUM_SIZE];
+ bool negative;
+ size_t i;
+
+ negative = 0;
+
+ fmt_sscanf_state_skip_space(state);
+ c = fmt_sscanf_state_consume_string(state);
+
+ if (c == '-') {
+ negative = true;
+ c = fmt_sscanf_state_consume_string(state);
+ }
+
+ if (c == '0') {
+ c = fmt_sscanf_state_consume_string(state);
+
+ if ((c == 'x') || (c == 'X')) {
+ if (state->base == 0) {
+ state->base = 16;
+ }
+
+ if (state->base == 16) {
+ c = fmt_sscanf_state_consume_string(state);
+ } else {
+ fmt_sscanf_state_restore_string(state);
+ }
+ } else {
+ if (state->base == 0) {
+ state->base = 8;
+ }
+ }
+ }
+
+ i = 0;
+
+ while (c != '\0') {
+ if (state->base == 8) {
+ if (!((c >= '0') && (c <= '7'))) {
+ break;
+ }
+ } else if (state->base == 16) {
+ if (!fmt_isxdigit(c)) {
+ break;
+ }
+ } else {
+ if (!fmt_isdigit(c)) {
+ break;
+ }
+ }
+
+ /* XXX Standard sscanf provides no way to cleanly handle overflows */
+ if (i < (ARRAY_SIZE(buf) - 1)) {
+ buf[i] = c;
+ } else if (i == (ARRAY_SIZE(buf) - 1)) {
+ strcpy(buf, "1");
+ negative = true;
+ }
+
+ i++;
+ c = fmt_sscanf_state_consume_string(state);
+ }
+
+ fmt_sscanf_state_restore_string(state);
+
+ if (state->flags & FMT_FORMAT_DISCARD) {
+ return 0;
+ }
+
+ if (i == 0) {
+ if (c == '\0') {
+ fmt_sscanf_state_report_error(state);
+ return ERR_FORMAT;
+ }
+
+ buf[0] = '0';
+ i = 1;
+ }
+
+ if (i < ARRAY_SIZE(buf)) {
+ buf[i] = '\0';
+ i--;
+ } else {
+ i = strlen(buf) - 1;
+ }
+
+ n = 0;
+
+#ifndef __LP64__
+ if (state->modifier == FMT_MODIFIER_LONGLONG) {
+#endif /* __LP64__ */
+ m = 1;
+ tmp = 0;
+
+ while (&buf[i] >= buf) {
+ tmp += fmt_atoi(buf[i]) * m;
+
+ if (tmp < n) {
+ n = 1;
+ negative = true;
+ break;
+ }
+
+ n = tmp;
+ m *= state->base;
+ i--;
+ }
+#ifndef __LP64__
+ } else {
+ unsigned long _n, _m, _tmp;
+
+ _n = 0;
+ _m = 1;
+ _tmp = 0;
+
+ while (&buf[i] >= buf) {
+ _tmp += fmt_atoi(buf[i]) * _m;
+
+ if (_tmp < _n) {
+ _n = 1;
+ negative = true;
+ break;
+ }
+
+ _n = _tmp;
+ _m *= state->base;
+ i--;
+ }
+
+ n = _n;
+ }
+#endif /* __LP64__ */
+
+ if (negative) {
+ n = -n;
+ }
+
+ switch (state->modifier) {
+ case FMT_MODIFIER_CHAR:
+ if (state->flags & FMT_FORMAT_CONV_SIGNED) {
+ *va_arg(state->ap, char *) = n;
+ } else {
+ *va_arg(state->ap, unsigned char *) = n;
+ }
+
+ break;
+ case FMT_MODIFIER_SHORT:
+ if (state->flags & FMT_FORMAT_CONV_SIGNED) {
+ *va_arg(state->ap, short *) = n;
+ } else {
+ *va_arg(state->ap, unsigned short *) = n;
+ }
+
+ break;
+ case FMT_MODIFIER_LONG:
+ if (state->flags & FMT_FORMAT_CONV_SIGNED) {
+ *va_arg(state->ap, long *) = n;
+ } else {
+ *va_arg(state->ap, unsigned long *) = n;
+ }
+
+ break;
+ case FMT_MODIFIER_LONGLONG:
+ if (state->flags & FMT_FORMAT_CONV_SIGNED) {
+ *va_arg(state->ap, long long *) = n;
+ } else {
+ *va_arg(state->ap, unsigned long long *) = n;
+ }
+
+ break;
+ case FMT_MODIFIER_PTR:
+ *va_arg(state->ap, uintptr_t *) = n;
+ break;
+ case FMT_MODIFIER_SIZE:
+ *va_arg(state->ap, size_t *) = n;
+ break;
+ case FMT_MODIFIER_PTRDIFF:
+ *va_arg(state->ap, ptrdiff_t *) = n;
+ break;
+ default:
+ if (state->flags & FMT_FORMAT_CONV_SIGNED) {
+ *va_arg(state->ap, int *) = n;
+ } else {
+ *va_arg(state->ap, unsigned int *) = n;
+ }
+ }
+
+ fmt_sscanf_state_report_conv(state);
+ return 0;
+}
+
+static int
+fmt_sscanf_state_produce_char(struct fmt_sscanf_state *state)
+{
+ char c, *dest;
+ int i, width;
+
+ if (state->flags & FMT_FORMAT_DISCARD) {
+ dest = NULL;
+ } else {
+ dest = va_arg(state->ap, char *);
+ }
+
+ if (state->flags & FMT_FORMAT_CHECK_WIDTH) {
+ width = state->width;
+ } else {
+ width = 1;
+ }
+
+ for (i = 0; i < width; i++) {
+ c = fmt_sscanf_state_consume_string(state);
+
+ if ((c == '\0') || (c == EOF)) {
+ break;
+ }
+
+ if (dest != NULL) {
+ *dest = c;
+ dest++;
+ }
+ }
+
+ if (i < width) {
+ fmt_sscanf_state_restore_string(state);
+ }
+
+ if (dest != NULL) {
+ fmt_sscanf_state_report_conv(state);
+ }
+
+ return 0;
+}
+
+static int
+fmt_sscanf_state_produce_str(struct fmt_sscanf_state *state)
+{
+ const char *orig;
+ char c, *dest;
+
+ orig = state->str;
+
+ fmt_sscanf_state_skip_space(state);
+
+ if (state->flags & FMT_FORMAT_DISCARD) {
+ dest = NULL;
+ } else {
+ dest = va_arg(state->ap, char *);
+ }
+
+ for (;;) {
+ c = fmt_sscanf_state_consume_string(state);
+
+ if ((c == '\0') || (c == ' ') || (c == EOF)) {
+ break;
+ }
+
+ if (dest != NULL) {
+ *dest = c;
+ dest++;
+ }
+ }
+
+ fmt_sscanf_state_restore_string(state);
+
+ if (state->str == orig) {
+ fmt_sscanf_state_report_error(state);
+ return ERR_FORMAT;
+ }
+
+ if (dest != NULL) {
+ *dest = '\0';
+ fmt_sscanf_state_report_conv(state);
+ }
+
+ return 0;
+}
+
+static int
+fmt_sscanf_state_produce_nrchars(struct fmt_sscanf_state *state)
+{
+ *va_arg(state->ap, int *) = state->str - state->start;
+ return 0;
+}
+
+static int
+fmt_sscanf_state_produce(struct fmt_sscanf_state *state)
+{
+ switch (state->specifier) {
+ case FMT_SPECIFIER_INT:
+ return fmt_sscanf_state_produce_int(state);
+ case FMT_SPECIFIER_CHAR:
+ return fmt_sscanf_state_produce_char(state);
+ case FMT_SPECIFIER_STR:
+ return fmt_sscanf_state_produce_str(state);
+ case FMT_SPECIFIER_NRCHARS:
+ return fmt_sscanf_state_produce_nrchars(state);
+ case FMT_SPECIFIER_PERCENT:
+ fmt_sscanf_state_skip_space(state);
+ return fmt_sscanf_state_discard_char(state, '%');
+ default:
+ fmt_sscanf_state_report_error(state);
+ return ERR_FORMAT;
+ }
+}
+
+int
+fmt_sscanf(const char *str, const char *format, ...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, format);
+ ret = fmt_vsscanf(str, format, ap);
+ va_end(ap);
+
+ return ret;
+}
+
+int
+fmt_vsscanf(const char *str, const char *format, va_list ap)
+{
+ struct fmt_sscanf_state state;
+ int error;
+
+ fmt_sscanf_state_init(&state, str, format, ap);
+
+ for (;;) {
+ error = fmt_sscanf_state_consume(&state);
+
+ if (error == ERR_AGAIN) {
+ continue;
+ } else if (error) {
+ break;
+ }
+
+ error = fmt_sscanf_state_produce(&state);
+
+ if (error) {
+ break;
+ }
+ }
+
+ return fmt_sscanf_state_finalize(&state);
+}
diff --git a/fmt.h b/fmt.h
new file mode 100644
index 0000000..b90ace7
--- /dev/null
+++ b/fmt.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2010-2017 Richard Braun.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ * Formatted string functions.
+ *
+ * The functions provided by this module implement a subset of the standard
+ * sprintf- and sscanf-like functions.
+ *
+ * sprintf:
+ * - flags: # 0 - ' ' (space) +
+ * - field width is supported
+ * - precision is supported
+ *
+ * sscanf:
+ * - flags: *
+ * - field width is supported
+ *
+ * common:
+ * - modifiers: hh h l ll z t
+ * - specifiers: d i o u x X c s p n %
+ *
+ *
+ * Upstream site with license notes :
+ * http://git.sceen.net/rbraun/librbraun.git/
+ */
+
+#ifndef _FMT_H
+#define _FMT_H
+
+#include <stdarg.h>
+
+int fmt_sprintf(char *str, const char *format, ...)
+ __attribute__((format(printf, 2, 3)));
+
+int fmt_vsprintf(char *str, const char *format, va_list ap)
+ __attribute__((format(printf, 2, 0)));
+
+int fmt_snprintf(char *str, size_t size, const char *format, ...)
+ __attribute__((format(printf, 3, 4)));
+
+int fmt_vsnprintf(char *str, size_t size, const char *format, va_list ap)
+ __attribute__((format(printf, 3, 0)));
+
+int fmt_sscanf(const char *str, const char *format, ...)
+ __attribute__((format(scanf, 2, 3)));
+
+int fmt_vsscanf(const char *str, const char *format, va_list ap)
+ __attribute__((format(scanf, 2, 0)));
+
+#endif /* _FMT_H */
diff --git a/test/test_fmt_sprintf.c b/test/test_fmt_sprintf.c
new file mode 100644
index 0000000..d18d8eb
--- /dev/null
+++ b/test/test_fmt_sprintf.c
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2010-2017 Richard Braun.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+#pragma GCC diagnostic ignored "-Wformat"
+
+#include "../check.h"
+#include "../fmt.h"
+#include "../macros.h"
+
+#define TEST_SPRINTF(format, ...) \
+MACRO_BEGIN \
+ char stra[256], strb[256]; \
+ int la, lb; \
+ la = snprintf(stra, sizeof(stra), format, ## __VA_ARGS__); \
+ lb = fmt_snprintf(strb, sizeof(strb), format, ## __VA_ARGS__); \
+ check(la == lb); \
+ check(strcmp(stra, strb) == 0); \
+MACRO_END
+
+int main(int argc, char *argv[])
+{
+ (void)argc;
+ (void)argv;
+
+#define FORMAT "%c"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 'a');
+#undef FORMAT
+
+#define FORMAT "%8c"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 'a');
+#undef FORMAT
+
+#define FORMAT "%-8c"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 'a');
+#undef FORMAT
+
+#define FORMAT "%.s"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, "12345");
+#undef FORMAT
+
+#define FORMAT "%.3s"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, "12345");
+#undef FORMAT
+
+#define FORMAT "%4.3s"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, "12345");
+#undef FORMAT
+
+#define FORMAT "%-4.3s"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, "12345");
+#undef FORMAT
+
+#define FORMAT "%3.4s"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, "12345");
+#undef FORMAT
+
+#define FORMAT "%-3.4s"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, "12345");
+#undef FORMAT
+
+#define FORMAT "%#o"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 123);
+#undef FORMAT
+
+#define FORMAT "%#x"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 123);
+#undef FORMAT
+
+#define FORMAT "%#X"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 123);
+#undef FORMAT
+
+#define FORMAT "%08d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 123);
+#undef FORMAT
+
+#define FORMAT "%-8d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 123);
+#undef FORMAT
+
+#define FORMAT "%-08d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 123);
+#undef FORMAT
+
+#define FORMAT "%0-8d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 123);
+#undef FORMAT
+
+#define FORMAT "% d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 123);
+#undef FORMAT
+
+#define FORMAT "%+d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 123);
+#undef FORMAT
+
+#define FORMAT "%+ d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 123);
+#undef FORMAT
+
+#define FORMAT "%12d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 123);
+#undef FORMAT
+
+#define FORMAT "%*d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 12, 123);
+#undef FORMAT
+
+#define FORMAT "%.12d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 123);
+#undef FORMAT
+
+#define FORMAT "%.012d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 123);
+#undef FORMAT
+
+#define FORMAT "%.*d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 12, 123);
+#undef FORMAT
+
+#define FORMAT "%.d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 123);
+#undef FORMAT
+
+#define FORMAT "%.*d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, -12, 123);
+#undef FORMAT
+
+#define FORMAT "%.4d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 123);
+#undef FORMAT
+
+#define FORMAT "%5.4d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 123);
+#undef FORMAT
+
+#define FORMAT "%4.5d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 123);
+#undef FORMAT
+
+#define FORMAT "%d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 0);
+#undef FORMAT
+
+#define FORMAT "%.0d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 0);
+#undef FORMAT
+
+#define FORMAT "%.0o"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 0);
+#undef FORMAT
+
+#define FORMAT "%.0x"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 0);
+#undef FORMAT
+
+#define FORMAT "%1.0d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 0);
+#undef FORMAT
+
+#define FORMAT "%08.0d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, -123);
+#undef FORMAT
+
+#define FORMAT "%08d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, -123);
+#undef FORMAT
+
+#define FORMAT "%08d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 123);
+#undef FORMAT
+
+#define FORMAT "%8d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 123);
+#undef FORMAT
+
+#define FORMAT "%8d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, -123);
+#undef FORMAT
+
+#define FORMAT "%.8d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, -123);
+#undef FORMAT
+
+#define FORMAT "%.80d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, -123);
+#undef FORMAT
+
+#define FORMAT "%-80d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, -123);
+#undef FORMAT
+
+#define FORMAT "%80d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, -123);
+#undef FORMAT
+
+#define FORMAT "%80.40d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, -123);
+#undef FORMAT
+
+#define FORMAT "%-+80.40d"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 123);
+#undef FORMAT
+
+#define FORMAT "%+x"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, -123);
+#undef FORMAT
+
+#define FORMAT "%#x"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 123);
+#undef FORMAT
+
+#define FORMAT "%#o"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 123);
+#undef FORMAT
+
+#define FORMAT "%p"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, "123");
+#undef FORMAT
+
+#define FORMAT "%#lx"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 0xdeadbeefL);
+#undef FORMAT
+
+#define FORMAT "%zd"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, (size_t)-123);
+#undef FORMAT
+
+#define FORMAT "%#llx"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 0xdeadbeefbadcafeLL);
+#undef FORMAT
+
+#define FORMAT "%llo"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT, 0xffffffffffffffffLL);
+#undef FORMAT
+
+#define FORMAT "%%"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT);
+#undef FORMAT
+
+#define FORMAT "%y"
+ TEST_SPRINTF("%s: " FORMAT, FORMAT);
+#undef FORMAT
+
+ char stra[10], strb[10];
+ int la, lb;
+ la = snprintf(stra, sizeof(stra), "%s", "123456789a");
+ lb = fmt_snprintf(strb, sizeof(strb), "%s", "123456789a");
+ check(la == lb);
+ check(strncmp(stra, strb, 10) == 0);
+
+#define FORMAT "12%n3%#08x4%n5"
+ int lc, ld;
+ sprintf(stra, FORMAT, &la, 123, &lb);
+ fmt_sprintf(strb, FORMAT, &lc, 123, &ld);
+ check(la == lc);
+ check(lb == ld);
+#undef FORMAT
+
+ la = snprintf(NULL, 0, "%s", "123");
+ lb = fmt_snprintf(NULL, 0, "%s", "123");
+ check(la == lb);
+
+ return 0;
+}
diff --git a/test/test_fmt_sscanf.c b/test/test_fmt_sscanf.c
new file mode 100644
index 0000000..b2a4e74
--- /dev/null
+++ b/test/test_fmt_sscanf.c
@@ -0,0 +1,726 @@
+/*
+ * Copyright (c) 2010-2017 Richard Braun.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#pragma GCC diagnostic ignored "-Wformat"
+
+#include "../check.h"
+#include "../fmt.h"
+#include "../macros.h"
+
+#define TEST_STR_SIZE 64
+
+#define TEST_INT 123
+#define TEST_INT64 1099511627775
+#define TEST_INT_OCTAL 0123
+#define TEST_INT_HEX 0x123
+#define TEST_INT_NEGATIVE -888
+#define TEST_INT_TOO_LARGE 894321568476132169879463132164984654
+#define TEST_STR "test"
+
+static void
+test_1(void)
+{
+ int reta, retb;
+ int ia, ib;
+
+#define STRING QUOTE(TEST_INT)
+#define FORMAT "%d"
+ reta = sscanf(STRING, FORMAT, &ia);
+ retb = fmt_sscanf(STRING, FORMAT, &ib);
+ check(reta == retb);
+ check(ia == ib);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_2(void)
+{
+ int reta, retb;
+
+#define STRING "abc"
+#define FORMAT "abc"
+ reta = sscanf(STRING, FORMAT);
+ retb = fmt_sscanf(STRING, FORMAT);
+ check(reta == retb);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_3(void)
+{
+ int reta, retb;
+ int ia, ib;
+
+#define STRING "ab" QUOTE(TEST_INT) "c"
+#define FORMAT "ab%dc"
+ reta = sscanf(STRING, FORMAT, &ia);
+ retb = fmt_sscanf(STRING, FORMAT, &ib);
+ check(reta == retb);
+ check(ia == ib);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_4(void)
+{
+ int reta, retb;
+ int ia, ib;
+
+#define STRING "ab" QUOTE(TEST_INT) "c"
+#define FORMAT "az%dc"
+ reta = sscanf(STRING, FORMAT, &ia);
+ retb = fmt_sscanf(STRING, FORMAT, &ib);
+ check(reta == retb);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_5(void)
+{
+ int reta, retb;
+ int ia, ib;
+
+#define STRING "abc"
+#define FORMAT "abc%d"
+ reta = sscanf(STRING, FORMAT, &ia);
+ retb = fmt_sscanf(STRING, FORMAT, &ib);
+ check(reta == retb);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_6(void)
+{
+ int reta, retb;
+ int ia, ib;
+ int ja, jb;
+
+#define STRING "a" QUOTE(TEST_INT_TOO_LARGE) "b" QUOTE(TEST_INT) "c"
+#define FORMAT "a%db%dc"
+ reta = sscanf(STRING, FORMAT, &ia, &ja);
+ retb = fmt_sscanf(STRING, FORMAT, &ib, &jb);
+ check(reta == retb);
+ check(ja == jb);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_7(void)
+{
+ unsigned int ia, ib;
+ int reta, retb;
+
+#define STRING QUOTE(TEST_INT_NEGATIVE)
+#define FORMAT "%u"
+ reta = sscanf(STRING, FORMAT, &ia);
+ retb = fmt_sscanf(STRING, FORMAT, &ib);
+ check(reta == retb);
+ check(ia == ib);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_8(void)
+{
+ unsigned int ia, ib;
+ int reta, retb;
+
+#define STRING " " QUOTE(TEST_INT)
+#define FORMAT "%u"
+ reta = sscanf(STRING, FORMAT, &ia);
+ retb = fmt_sscanf(STRING, FORMAT, &ib);
+ check(reta == retb);
+ check(ia == ib);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_9(void)
+{
+ int reta, retb;
+ int ia, ib;
+
+#define STRING " " QUOTE(TEST_INT_NEGATIVE)
+#define FORMAT "%d"
+ reta = sscanf(STRING, FORMAT, &ia);
+ retb = fmt_sscanf(STRING, FORMAT, &ib);
+ check(reta == retb);
+ check(ia == ib);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_10(void)
+{
+ int reta, retb;
+ int ia, ib;
+
+#define STRING "a%b" QUOTE(TEST_INT)
+#define FORMAT "a%%b%d"
+ reta = sscanf(STRING, FORMAT, &ia);
+ retb = fmt_sscanf(STRING, FORMAT, &ib);
+ check(reta == retb);
+ check(ia == ib);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_11(void)
+{
+ int reta, retb;
+ char ia, ib;
+
+#define STRING QUOTE(TEST_INT)
+#define FORMAT "%hhd"
+ reta = sscanf(STRING, FORMAT, &ia);
+ retb = fmt_sscanf(STRING, FORMAT, &ib);
+ check(reta == retb);
+ check(ia == ib);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_12(void)
+{
+ unsigned short ia, ib;
+ int reta, retb;
+
+#define STRING QUOTE(TEST_INT)
+#define FORMAT "%hu"
+ reta = sscanf(STRING, FORMAT, &ia);
+ retb = fmt_sscanf(STRING, FORMAT, &ib);
+ check(reta == retb);
+ check(ia == ib);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_13(void)
+{
+ int reta, retb;
+ long ia, ib;
+
+#define STRING QUOTE(TEST_INT)
+#define FORMAT "%ld"
+ reta = sscanf(STRING, FORMAT, &ia);
+ retb = fmt_sscanf(STRING, FORMAT, &ib);
+ check(reta == retb);
+ check(ia == ib);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_14(void)
+{
+ unsigned long long ia, ib;
+ int reta, retb;
+
+#define STRING QUOTE(TEST_INT)
+#define FORMAT "%llu"
+ reta = sscanf(STRING, FORMAT, &ia);
+ retb = fmt_sscanf(STRING, FORMAT, &ib);
+ check(reta == retb);
+ check(ia == ib);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_15(void)
+{
+ unsigned long long ia, ib;
+ int reta, retb;
+
+#define STRING ""
+#define FORMAT "%llu"
+ reta = sscanf(STRING, FORMAT, &ia);
+ retb = fmt_sscanf(STRING, FORMAT, &ib);
+ check(reta == retb);
+ check(ia == ib);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_16(void)
+{
+ int reta, retb;
+ int ia, ib;
+
+#define STRING QUOTE(TEST_INT_OCTAL)
+#define FORMAT "%i"
+ reta = sscanf(STRING, FORMAT, &ia);
+ retb = fmt_sscanf(STRING, FORMAT, &ib);
+ check(reta == retb);
+ check(ia == ib);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_17(void)
+{
+ int reta, retb;
+ int ia, ib;
+
+#define STRING QUOTE(TEST_INT_OCTAL) "abc"
+#define FORMAT "%iabc"
+ reta = sscanf(STRING, FORMAT, &ia);
+ retb = fmt_sscanf(STRING, FORMAT, &ib);
+ check(reta == retb);
+ check(ia == ib);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_18(void)
+{
+ int reta, retb;
+ int ia, ib;
+
+#define STRING QUOTE(TEST_INT_HEX)
+#define FORMAT "%i"
+ reta = sscanf(STRING, FORMAT, &ia);
+ retb = fmt_sscanf(STRING, FORMAT, &ib);
+ check(reta == retb);
+ check(ia == ib);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_19(void)
+{
+ int reta, retb;
+ int ia, ib;
+
+#define STRING QUOTE(TEST_INT_HEX) "abcxyz"
+#define FORMAT "%iabcxyz"
+ reta = sscanf(STRING, FORMAT, &ia);
+ retb = fmt_sscanf(STRING, FORMAT, &ib);
+ check(reta == retb);
+ check(ia == ib);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_20(void)
+{
+ int reta, retb;
+ int ia, ib;
+
+#define STRING QUOTE(TEST_INT)
+#define FORMAT "%o"
+ reta = sscanf(STRING, FORMAT, &ia);
+ retb = fmt_sscanf(STRING, FORMAT, &ib);
+ check(reta == retb);
+ check(ia == ib);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_21(void)
+{
+ int reta, retb;
+ int ia, ib;
+
+#define STRING QUOTE(TEST_INT)
+#define FORMAT "%x"
+ reta = sscanf(STRING, FORMAT, &ia);
+ retb = fmt_sscanf(STRING, FORMAT, &ib);
+ check(reta == retb);
+ check(ia == ib);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_22(void)
+{
+ int reta, retb;
+ int ia, ib;
+
+#define STRING QUOTE(TEST_INT_HEX)
+#define FORMAT "%x"
+ reta = sscanf(STRING, FORMAT, &ia);
+ retb = fmt_sscanf(STRING, FORMAT, &ib);
+ check(reta == retb);
+ check(ia == ib);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_23(void)
+{
+ int reta, retb;
+ int ia, ib;
+
+#define STRING QUOTE(-TEST_INT_HEX)
+#define FORMAT "%x"
+ reta = sscanf(STRING, FORMAT, &ia);
+ retb = fmt_sscanf(STRING, FORMAT, &ib);
+ check(reta == retb);
+ check(ia == ib);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_24(void)
+{
+ int reta, retb;
+ int ia, ib;
+
+#define STRING "a" QUOTE(TEST_INT_HEX) "b"
+#define FORMAT "a%ob"
+ reta = sscanf(STRING, FORMAT, &ia);
+ retb = fmt_sscanf(STRING, FORMAT, &ib);
+ check(reta == retb);
+ check(ia == ib);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_25(void)
+{
+ int reta, retb;
+ int ia, ib;
+ int ja, jb;
+
+#define STRING "a" QUOTE(TEST_INT_OCTAL) "b" QUOTE(TEST_INT_HEX) "c"
+#define FORMAT "a%ob%xc"
+ reta = sscanf(STRING, FORMAT, &ia, &ja);
+ retb = fmt_sscanf(STRING, FORMAT, &ib, &jb);
+ check(reta == retb);
+ check(ia == ib);
+ check(ja == jb);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_26(void)
+{
+ int reta, retb;
+ int ia, ib;
+ int ja, jb;
+
+#define STRING "a" QUOTE(TEST_INT_HEX) "b" QUOTE(TEST_INT_OCTAL) "c"
+#define FORMAT "a%xb%oc"
+ reta = sscanf(STRING, FORMAT, &ia, &ja);
+ retb = fmt_sscanf(STRING, FORMAT, &ib, &jb);
+ check(reta == retb);
+ check(ia == ib);
+ check(ja == jb);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_27(void)
+{
+ int reta, retb;
+ int ia, ib;
+ int ja, jb;
+
+#define STRING QUOTE(TEST_INT) " " QUOTE(TEST_INT)
+#define FORMAT " %d %d "
+ reta = sscanf(STRING, FORMAT, &ia, &ja);
+ retb = fmt_sscanf(STRING, FORMAT, &ib, &jb);
+ check(reta == retb);
+ check(ia == ib);
+ check(ja == jb);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_28(void)
+{
+ int reta, retb;
+ int ia, ib;
+
+#define STRING QUOTE(TEST_INT_HEX)
+#define FORMAT "%p"
+ reta = sscanf(STRING, FORMAT, &ia);
+ retb = fmt_sscanf(STRING, FORMAT, &ib);
+ check(reta == retb);
+ check(ia == ib);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_29(void)
+{
+ int reta, retb;
+ int ia, ib;
+
+#define STRING QUOTE(TEST_INT) QUOTE(TEST_INT_NEGATIVE)
+#define FORMAT "%*d%d"
+ reta = sscanf(STRING, FORMAT, &ia);
+ retb = fmt_sscanf(STRING, FORMAT, &ib);
+ check(reta == retb);
+ check(ia == ib);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_30(void)
+{
+ int reta, retb;
+ int ia, ib;
+
+#define STRING QUOTE(TEST_INT_TOO_LARGE)
+#define FORMAT "%3d"
+ reta = sscanf(STRING, FORMAT, &ia);
+ retb = fmt_sscanf(STRING, FORMAT, &ib);
+ check(reta == retb);
+ check(ia == ib);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_31(void)
+{
+ int reta, retb;
+ int ia, ib;
+
+#define STRING "-0x" QUOTE(TEST_INT_TOO_LARGE)
+#define FORMAT "%5i"
+ reta = sscanf(STRING, FORMAT, &ia);
+ retb = fmt_sscanf(STRING, FORMAT, &ib);
+ check(reta == retb);
+ check(ia == ib);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_32(void)
+{
+ int reta, retb;
+ char stra[TEST_STR_SIZE], strb[TEST_STR_SIZE];
+
+ memset(stra, 0, sizeof(stra));
+ memset(strb, 0, sizeof(strb));
+
+#define STRING TEST_STR
+#define FORMAT "%s"
+ reta = sscanf(STRING, FORMAT, &stra);
+ retb = fmt_sscanf(STRING, FORMAT, &strb);
+ check(reta == retb);
+ check(strcmp(stra, strb) == 0);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_33(void)
+{
+ int reta, retb;
+ char stra[TEST_STR_SIZE], strb[TEST_STR_SIZE];
+
+ memset(stra, 0, sizeof(stra));
+ memset(strb, 0, sizeof(strb));
+
+#define STRING TEST_STR
+#define FORMAT "%*s%s"
+ reta = sscanf(STRING, FORMAT, &stra);
+ retb = fmt_sscanf(STRING, FORMAT, &strb);
+ check(reta == retb);
+ check(strcmp(stra, strb) == 0);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_34(void)
+{
+ int reta, retb;
+ char stra[TEST_STR_SIZE], strb[TEST_STR_SIZE];
+
+ memset(stra, 0, sizeof(stra));
+ memset(strb, 0, sizeof(strb));
+
+#define STRING "abc " TEST_STR
+#define FORMAT "%*s%s"
+ reta = sscanf(STRING, FORMAT, &stra);
+ retb = fmt_sscanf(STRING, FORMAT, &strb);
+ check(reta == retb);
+ check(strcmp(stra, strb) == 0);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_35(void)
+{
+ int reta, retb;
+ char stra[TEST_STR_SIZE], strb[TEST_STR_SIZE];
+
+ memset(stra, 0, sizeof(stra));
+ memset(strb, 0, sizeof(strb));
+
+#define STRING "abc " TEST_STR
+#define FORMAT "%*s%3s"
+ reta = sscanf(STRING, FORMAT, &stra);
+ retb = fmt_sscanf(STRING, FORMAT, &strb);
+ check(reta == retb);
+ check(strcmp(stra, strb) == 0);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_36(void)
+{
+ int reta, retb;
+ char stra[TEST_STR_SIZE], strb[TEST_STR_SIZE];
+
+ memset(stra, 0, sizeof(stra));
+ memset(strb, 0, sizeof(strb));
+
+#define STRING TEST_STR
+#define FORMAT "%*c%c"
+ reta = sscanf(STRING, FORMAT, &stra);
+ retb = fmt_sscanf(STRING, FORMAT, &strb);
+ check(reta == retb);
+ check(strcmp(stra, strb) == 0);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_37(void)
+{
+ int reta, retb;
+ char stra[TEST_STR_SIZE], strb[TEST_STR_SIZE];
+
+ memset(stra, 0, sizeof(stra));
+ memset(strb, 0, sizeof(strb));
+
+#define STRING "a"
+#define FORMAT "%5c"
+ reta = sscanf(STRING, FORMAT, &stra);
+ retb = fmt_sscanf(STRING, FORMAT, &strb);
+ check(reta == retb);
+ check(strcmp(stra, strb) == 0);
+#undef FORMAT
+#undef STRING
+}
+
+static void
+test_38(void)
+{
+ int reta, retb;
+ int ia, ib;
+ int na, nb;
+ int ja, jb;
+
+#define STRING QUOTE(TEST_INT) ":abc:" QUOTE(TEST_INT_NEGATIVE)
+#define FORMAT "%d:abc:%n%d"
+ reta = sscanf(STRING, FORMAT, &ia, &na, &ja);
+ retb = fmt_sscanf(STRING, FORMAT, &ib, &nb, &jb);
+ check(reta == retb);
+ check(ia == ib);
+ check(na == nb);
+ check(ja == jb);
+#undef FORMAT
+#undef STRING
+}
+
+int
+main(int argc, char *argv[])
+{
+ (void)argc;
+ (void)argv;
+
+ test_1();
+ test_2();
+ test_3();
+ test_4();
+ test_5();
+ test_6();
+ test_7();
+ test_8();
+ test_9();
+ test_10();
+ test_11();
+ test_12();
+ test_13();
+ test_14();
+ test_15();
+ test_16();
+ test_17();
+ test_18();
+ test_19();
+ test_20();
+ test_21();
+ test_22();
+ test_23();
+ test_24();
+ test_25();
+ test_26();
+ test_27();
+ test_28();
+ test_29();
+ test_30();
+ test_31();
+ test_32();
+ test_33();
+ test_34();
+ test_35();
+ test_36();
+ test_37();
+ test_38();
+
+ return 0;
+}