Blob Blame History Raw
/* Test gmp_scanf and related functions.

Copyright 2001-2004 Free Software Foundation, Inc.

This file is part of the GNU MP Library test suite.

The GNU MP Library test suite is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 3 of the License,
or (at your option) any later version.

The GNU MP Library test suite is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
Public License for more details.

You should have received a copy of the GNU General Public License along with
the GNU MP Library test suite.  If not, see https://www.gnu.org/licenses/.  */


/* Usage: t-scanf [-s]

   -s  Check the data against the system scanf, where possible.  This is
       only an option since we don't want to fail if the system scanf is
       faulty or strange.

   There's some fairly unattractive repetition between check_z, check_q and
   check_f, but enough differences to make a common loop or a set of macros
   seem like too much trouble. */

#include "config.h"	/* needed for the HAVE_, could also move gmp incls */

#include <stdarg.h>

#include <stddef.h>    /* for ptrdiff_t */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if HAVE_INTTYPES_H
# include <inttypes.h> /* for intmax_t */
#else
# if HAVE_STDINT_H
#  include <stdint.h>
# endif
#endif

#if HAVE_UNISTD_H
#include <unistd.h>  /* for unlink */
#endif

#include "gmp.h"
#include "gmp-impl.h"
#include "tests.h"


#define TEMPFILE  "t-scanf.tmp"

int   option_libc_scanf = 0;

typedef int (*fun_t) (const char *, const char *, void *, void *);


/* This problem was seen on powerpc7450-apple-darwin7.0.0, sscanf returns 0
   where it should return EOF.  A workaround in gmp_sscanf would be a bit
   tedious, and since this is a rather obvious libc bug, quite likely
   affecting other programs, we'll just suppress affected tests for now.  */
int
test_sscanf_eof_ok (void)
{
  static int  result = -1;

  if (result == -1)
    {
      int  x;
      if (sscanf ("", "%d", &x) == EOF)
        {
          result = 1;
        }
      else
        {
          printf ("Warning, sscanf(\"\",\"%%d\",&x) doesn't return EOF.\n");
          printf ("This affects gmp_sscanf, tests involving it will be suppressed.\n");
          printf ("You should try to get a fix for your libc.\n");
          result = 0;
        }
    }
  return result;
}


/* Convert fmt from a GMP scanf format string to an equivalent for a plain
   libc scanf, for example "%Zd" becomes "%ld".  Return 1 if this succeeds,
   0 if it cannot (or should not) be done.  */
int
libc_scanf_convert (char *fmt)
{
  char  *p = fmt;

  if (! option_libc_scanf)
    return 0;

  for ( ; *fmt != '\0'; fmt++)
    {
      switch (*fmt) {
      case 'F':
      case 'Q':
      case 'Z':
        /* transmute */
        *p++ = 'l';
        break;
      default:
        *p++ = *fmt;
        break;
      }
    }
  *p = '\0';
  return 1;
}


long  got_ftell;
int   fromstring_next_c;

/* Call gmp_fscanf, reading the "input" string data provided. */
int
fromstring_gmp_fscanf (const char *input, const char *fmt, ...)
{
  va_list  ap;
  FILE     *fp;
  int      ret;
  va_start (ap, fmt);

  fp = fopen (TEMPFILE, "w+");
  ASSERT_ALWAYS (fp != NULL);
  ASSERT_ALWAYS (fputs (input, fp) != EOF);
  ASSERT_ALWAYS (fflush (fp) == 0);
  rewind (fp);

  ret = gmp_vfscanf (fp, fmt, ap);
  got_ftell = ftell (fp);
  ASSERT_ALWAYS (got_ftell != -1L);

  fromstring_next_c = getc (fp);

  ASSERT_ALWAYS (fclose (fp) == 0);
  va_end (ap);
  return ret;
}


int
fun_gmp_sscanf (const char *input, const char *fmt, void *a1, void *a2)
{
  if (a2 == NULL)
    return gmp_sscanf (input, fmt, a1);
  else
    return gmp_sscanf (input, fmt, a1, a2);
}

int
fun_gmp_fscanf (const char *input, const char *fmt, void *a1, void *a2)
{
  if (a2 == NULL)
    return fromstring_gmp_fscanf (input, fmt, a1);
  else
    return fromstring_gmp_fscanf (input, fmt, a1, a2);
}


int
fun_fscanf (const char *input, const char *fmt, void *a1, void *a2)
{
  FILE  *fp;
  int   ret;

  fp = fopen (TEMPFILE, "w+");
  ASSERT_ALWAYS (fp != NULL);
  ASSERT_ALWAYS (fputs (input, fp) != EOF);
  ASSERT_ALWAYS (fflush (fp) == 0);
  rewind (fp);

  if (a2 == NULL)
    ret = fscanf (fp, fmt, a1);
  else
    ret = fscanf (fp, fmt, a1, a2);

  got_ftell = ftell (fp);
  ASSERT_ALWAYS (got_ftell != -1L);

  fromstring_next_c = getc (fp);

  ASSERT_ALWAYS (fclose (fp) == 0);
  return ret;
}


/* On various old systems, for instance HP-UX 9, the C library sscanf needs
   to be able to write into the input string.  Ensure that this is possible,
   when gcc is putting the test data into a read-only section.

   Actually we ought to only need this under SSCANF_WRITABLE_INPUT from
   configure, but it's just as easy to do it unconditionally, and in any
   case this code is only executed under the -s option.  */

int
fun_sscanf (const char *input, const char *fmt, void *a1, void *a2)
{
  char    *input_writable;
  size_t  size;
  int     ret;

  size = strlen (input) + 1;
  input_writable = (char *) (*__gmp_allocate_func) (size);
  memcpy (input_writable, input, size);

  if (a2 == NULL)
    ret = sscanf (input_writable, fmt, a1);
  else
    ret = sscanf (input_writable, fmt, a1, a2);

  (*__gmp_free_func) (input_writable, size);
  return ret;
}


/* whether the format string consists entirely of ignored fields */
int
fmt_allignore (const char *fmt)
{
  int  saw_star = 1;
  for ( ; *fmt != '\0'; fmt++)
    {
      switch (*fmt) {
      case '%':
        if (! saw_star)
          return 0;
        saw_star = 0;
        break;
      case '*':
        saw_star = 1;
        break;
      }
    }
  return 1;
}

void
check_z (void)
{
  static const struct {
    const char  *fmt;
    const char  *input;
    const char  *want;
    int         want_ret;
    long        want_ftell;
    int         want_upto;
    int         not_glibc;

  } data[] = {

    { "%Zd",    "0",    "0", 1, -1, -1 },
    { "%Zd",    "1",    "1", 1, -1, -1 },
    { "%Zd",  "123",  "123", 1, -1, -1 },
    { "%Zd",   "+0",    "0", 1, -1, -1 },
    { "%Zd",   "+1",    "1", 1, -1, -1 },
    { "%Zd", "+123",  "123", 1, -1, -1 },
    { "%Zd",   "-0",    "0", 1, -1, -1 },
    { "%Zd",   "-1",   "-1", 1, -1, -1 },
    { "%Zd", "-123", "-123", 1, -1, -1 },

    { "%Zo",    "0",    "0", 1, -1, -1 },
    { "%Zo",  "173",  "123", 1, -1, -1 },
    { "%Zo",   "+0",    "0", 1, -1, -1 },
    { "%Zo", "+173",  "123", 1, -1, -1 },
    { "%Zo",   "-0",    "0", 1, -1, -1 },
    { "%Zo", "-173", "-123", 1, -1, -1 },

    { "%Zx",    "0",    "0", 1, -1, -1 },
    { "%Zx",   "7b",  "123", 1, -1, -1 },
    { "%Zx",   "7b",  "123", 1, -1, -1 },
    { "%Zx",   "+0",    "0", 1, -1, -1 },
    { "%Zx",  "+7b",  "123", 1, -1, -1 },
    { "%Zx",  "+7b",  "123", 1, -1, -1 },
    { "%Zx",   "-0",   "-0", 1, -1, -1 },
    { "%Zx",  "-7b", "-123", 1, -1, -1 },
    { "%Zx",  "-7b", "-123", 1, -1, -1 },
    { "%ZX",    "0",    "0", 1, -1, -1 },
    { "%ZX",   "7b",  "123", 1, -1, -1 },
    { "%ZX",   "7b",  "123", 1, -1, -1 },
    { "%ZX",   "+0",    "0", 1, -1, -1 },
    { "%ZX",  "+7b",  "123", 1, -1, -1 },
    { "%ZX",  "+7b",  "123", 1, -1, -1 },
    { "%ZX",   "-0",   "-0", 1, -1, -1 },
    { "%ZX",  "-7b", "-123", 1, -1, -1 },
    { "%ZX",  "-7b", "-123", 1, -1, -1 },
    { "%Zx",    "0",    "0", 1, -1, -1 },
    { "%Zx",   "7B",  "123", 1, -1, -1 },
    { "%Zx",   "7B",  "123", 1, -1, -1 },
    { "%Zx",   "+0",    "0", 1, -1, -1 },
    { "%Zx",  "+7B",  "123", 1, -1, -1 },
    { "%Zx",  "+7B",  "123", 1, -1, -1 },
    { "%Zx",   "-0",   "-0", 1, -1, -1 },
    { "%Zx",  "-7B", "-123", 1, -1, -1 },
    { "%Zx",  "-7B", "-123", 1, -1, -1 },
    { "%ZX",    "0",    "0", 1, -1, -1 },
    { "%ZX",   "7B",  "123", 1, -1, -1 },
    { "%ZX",   "7B",  "123", 1, -1, -1 },
    { "%ZX",   "+0",    "0", 1, -1, -1 },
    { "%ZX",  "+7B",  "123", 1, -1, -1 },
    { "%ZX",  "+7B",  "123", 1, -1, -1 },
    { "%ZX",   "-0",   "-0", 1, -1, -1 },
    { "%ZX",  "-7B", "-123", 1, -1, -1 },
    { "%ZX",  "-7B", "-123", 1, -1, -1 },

    { "%Zi",    "0",    "0", 1, -1, -1 },
    { "%Zi",    "1",    "1", 1, -1, -1 },
    { "%Zi",  "123",  "123", 1, -1, -1 },
    { "%Zi",   "+0",    "0", 1, -1, -1 },
    { "%Zi",   "+1",    "1", 1, -1, -1 },
    { "%Zi", "+123",  "123", 1, -1, -1 },
    { "%Zi",   "-0",    "0", 1, -1, -1 },
    { "%Zi",   "-1",   "-1", 1, -1, -1 },
    { "%Zi", "-123", "-123", 1, -1, -1 },

    { "%Zi",    "00",    "0", 1, -1, -1 },
    { "%Zi",  "0173",  "123", 1, -1, -1 },
    { "%Zi",   "+00",    "0", 1, -1, -1 },
    { "%Zi", "+0173",  "123", 1, -1, -1 },
    { "%Zi",   "-00",    "0", 1, -1, -1 },
    { "%Zi", "-0173", "-123", 1, -1, -1 },

    { "%Zi",    "0x0",    "0", 1, -1, -1 },
    { "%Zi",   "0x7b",  "123", 1, -1, -1 },
    { "%Zi",   "0x7b",  "123", 1, -1, -1 },
    { "%Zi",   "+0x0",    "0", 1, -1, -1 },
    { "%Zi",  "+0x7b",  "123", 1, -1, -1 },
    { "%Zi",  "+0x7b",  "123", 1, -1, -1 },
    { "%Zi",   "-0x0",   "-0", 1, -1, -1 },
    { "%Zi",  "-0x7b", "-123", 1, -1, -1 },
    { "%Zi",  "-0x7b", "-123", 1, -1, -1 },
    { "%Zi",    "0X0",    "0", 1, -1, -1 },
    { "%Zi",   "0X7b",  "123", 1, -1, -1 },
    { "%Zi",   "0X7b",  "123", 1, -1, -1 },
    { "%Zi",   "+0X0",    "0", 1, -1, -1 },
    { "%Zi",  "+0X7b",  "123", 1, -1, -1 },
    { "%Zi",  "+0X7b",  "123", 1, -1, -1 },
    { "%Zi",   "-0X0",   "-0", 1, -1, -1 },
    { "%Zi",  "-0X7b", "-123", 1, -1, -1 },
    { "%Zi",  "-0X7b", "-123", 1, -1, -1 },
    { "%Zi",    "0x0",    "0", 1, -1, -1 },
    { "%Zi",   "0x7B",  "123", 1, -1, -1 },
    { "%Zi",   "0x7B",  "123", 1, -1, -1 },
    { "%Zi",   "+0x0",    "0", 1, -1, -1 },
    { "%Zi",  "+0x7B",  "123", 1, -1, -1 },
    { "%Zi",  "+0x7B",  "123", 1, -1, -1 },
    { "%Zi",   "-0x0",   "-0", 1, -1, -1 },
    { "%Zi",  "-0x7B", "-123", 1, -1, -1 },
    { "%Zi",  "-0x7B", "-123", 1, -1, -1 },
    { "%Zi",    "0X0",    "0", 1, -1, -1 },
    { "%Zi",   "0X7B",  "123", 1, -1, -1 },
    { "%Zi",   "0X7B",  "123", 1, -1, -1 },
    { "%Zi",   "+0X0",    "0", 1, -1, -1 },
    { "%Zi",  "+0X7B",  "123", 1, -1, -1 },
    { "%Zi",  "+0X7B",  "123", 1, -1, -1 },
    { "%Zi",   "-0X0",   "-0", 1, -1, -1 },
    { "%Zi",  "-0X7B", "-123", 1, -1, -1 },
    { "%Zi",  "-0X7B", "-123", 1, -1, -1 },

    { "%Zd",    " 0",    "0", 1, -1, -1 },
    { "%Zd",   "  0",    "0", 1, -1, -1 },
    { "%Zd",  "   0",    "0", 1, -1, -1 },
    { "%Zd",   "\t0",    "0", 1, -1, -1 },
    { "%Zd", "\t\t0",    "0", 1, -1, -1 },

    { "hello%Zd",      "hello0",       "0", 1, -1, -1 },
    { "hello%Zd",      "hello 0",      "0", 1, -1, -1 },
    { "hello%Zd",      "hello \t0",    "0", 1, -1, -1 },
    { "hello%Zdworld", "hello 0world", "0", 1, -1, -1 },

    { "hello%*Zd",      "hello0",       "-999", 0, -1, -1 },
    { "hello%*Zd",      "hello 0",      "-999", 0, -1, -1 },
    { "hello%*Zd",      "hello \t0",    "-999", 0, -1, -1 },
    { "hello%*Zdworld", "hello 0world", "-999", 0, -1, -1 },

    { "%Zd",    "",     "-999", -1, -1, -555 },
    { "%Zd",    " ",    "-999", -1, -1, -555 },
    { " %Zd",   "",     "-999", -1, -1, -555 },
    { "xyz%Zd", "",     "-999", -1, -1, -555 },

    { "%*Zd",    "",     "-999", -1, -1, -555 },
    { " %*Zd",   "",     "-999", -1, -1, -555 },
    { "xyz%*Zd", "",     "-999", -1, -1, -555 },

    { "%Zd",    "xyz",  "0",     0, 0, -555 },

    /* match something, but invalid */
    { "%Zd",    "-",    "-999",  0, 1, -555 },
    { "%Zd",    "+",    "-999",  0, 1, -555 },
    { "xyz%Zd", "xyz-", "-999",  0, 4, -555 },
    { "xyz%Zd", "xyz+", "-999",  0, 4, -555 },
    { "%Zi",    "0x",   "-999",  0, 2, -555 },
    { "%Zi",    "0X",   "-999",  0, 2, -555 },
    { "%Zi",    "0x-",  "-999",  0, 2, -555 },
    { "%Zi",    "0X+",  "-999",  0, 2, -555 },
    { "%Zi",    "-0x",  "-999",  0, 3, -555 },
    { "%Zi",    "-0X",  "-999",  0, 3, -555 },
    { "%Zi",    "+0x",  "-999",  0, 3, -555 },
    { "%Zi",    "+0X",  "-999",  0, 3, -555 },

    { "%1Zi",  "1234", "1",    1, 1, 1 },
    { "%2Zi",  "1234", "12",   1, 2, 2 },
    { "%3Zi",  "1234", "123",  1, 3, 3 },
    { "%4Zi",  "1234", "1234", 1, 4, 4 },
    { "%5Zi",  "1234", "1234", 1, 4, 4 },
    { "%6Zi",  "1234", "1234", 1, 4, 4 },

    { "%1Zi",  "01234", "0",     1, 1, 1 },
    { "%2Zi",  "01234", "01",    1, 2, 2 },
    { "%3Zi",  "01234", "012",   1, 3, 3 },
    { "%4Zi",  "01234", "0123",  1, 4, 4 },
    { "%5Zi",  "01234", "01234", 1, 5, 5 },
    { "%6Zi",  "01234", "01234", 1, 5, 5 },
    { "%7Zi",  "01234", "01234", 1, 5, 5 },

    { "%1Zi",  "0x1234", "0",      1, 1, 1 },
    { "%2Zi",  "0x1234", "-999",   0, 2, -555 },
    { "%3Zi",  "0x1234", "0x1",    1, 3, 3 },
    { "%4Zi",  "0x1234", "0x12",   1, 4, 4 },
    { "%5Zi",  "0x1234", "0x123",  1, 5, 5 },
    { "%6Zi",  "0x1234", "0x1234", 1, 6, 6 },
    { "%7Zi",  "0x1234", "0x1234", 1, 6, 6 },
    { "%8Zi",  "0x1234", "0x1234", 1, 6, 6 },

    { "%%xyz%Zd",  "%xyz123",  "123", 1, -1, -1 },
    { "12%%34%Zd", "12%34567", "567", 1, -1, -1 },
    { "%%%%%Zd",   "%%123",    "123", 1, -1, -1 },

    /* various subtle EOF cases */
    { "x",       "",    "-999", EOF, 0, -555 },
    { " x",      "",    "-999", EOF, 0, -555 },
    { "xyz",     "",    "-999", EOF, 0, -555 },
    { " ",       "",    "-999",   0, 0,    0 },
    { " ",       " ",   "-999",   0, 1,    1 },
    { "%*Zd%Zd", "",    "-999", EOF, 0, -555 },
    { "%*Zd%Zd", "123", "-999", EOF, 3, -555 },
    { "x",       "x",   "-999",   0, 1,    1 },
    { "xyz",     "x",   "-999", EOF, 1, -555 },
    { "xyz",     "xy",  "-999", EOF, 2, -555 },
    { "xyz",     "xyz", "-999",   0, 3,    3 },
    { "%Zn",     "",    "0",      0, 0,    0 },
    { " %Zn",    "",    "0",      0, 0,    0 },
    { " x%Zn",   "",    "-999", EOF, 0, -555 },
    { "xyz%Zn",  "",    "-999", EOF, 0, -555 },
    { " x%Zn",   "",    "-999", EOF, 0, -555 },
    { " %Zn x",  " ",   "-999", EOF, 1, -555 },

    /* these seem to tickle a bug in glibc 2.2.4 */
    { " x",      " ",   "-999", EOF, 1, -555, 1 },
    { " xyz",    " ",   "-999", EOF, 1, -555, 1 },
    { " x%Zn",   " ",   "-999", EOF, 1, -555, 1 },
  };

  int         i, j, ignore;
  int         got_ret, want_ret, got_upto, want_upto;
  mpz_t       got, want;
  long        got_l, want_ftell;
  int         error = 0;
  fun_t       fun;
  const char  *name;
  char        fmt[128];

  mpz_init (got);
  mpz_init (want);

  for (i = 0; i < numberof (data); i++)
    {
      mpz_set_str_or_abort (want, data[i].want, 0);

      ASSERT_ALWAYS (strlen (data[i].fmt) + 2 < sizeof (fmt));
      strcpy (fmt, data[i].fmt);
      strcat (fmt, "%n");

      ignore = fmt_allignore (fmt);

      for (j = 0; j <= 3; j++)
        {
          want_ret = data[i].want_ret;

          want_ftell = data[i].want_ftell;
          if (want_ftell == -1)
            want_ftell = strlen (data[i].input);

          want_upto = data[i].want_upto;
          if (want_upto == -1)
            want_upto = strlen (data[i].input);

          switch (j) {
          case 0:
            name = "gmp_sscanf";
            fun = fun_gmp_sscanf;
            break;
          case 1:
            name = "gmp_fscanf";
            fun = fun_gmp_fscanf;
            break;
          case 2:
#ifdef __GLIBC__
            if (data[i].not_glibc)
              continue;
#endif
            if (! libc_scanf_convert (fmt))
              continue;
            name = "standard sscanf";
            fun = fun_sscanf;
            break;
          case 3:
#ifdef __GLIBC__
            if (data[i].not_glibc)
              continue;
#endif
            if (! libc_scanf_convert (fmt))
              continue;
            name = "standard fscanf";
            fun = fun_fscanf;
            break;
          default:
            ASSERT_ALWAYS (0);
            break;
          }

          got_upto = -555;
          got_ftell = -1L;

          switch (j) {
          case 0:
          case 1:
            mpz_set_si (got, -999L);
            if (ignore)
              got_ret = (*fun) (data[i].input, fmt, &got_upto, NULL);
            else
              got_ret = (*fun) (data[i].input, fmt, got, &got_upto);
            break;
          case 2:
          case 3:
            got_l = -999L;
            if (ignore)
              got_ret = (*fun) (data[i].input, fmt, &got_upto, NULL);
            else
              got_ret = (*fun) (data[i].input, fmt, &got_l, &got_upto);
            mpz_set_si (got, got_l);
            break;
          default:
            ASSERT_ALWAYS (0);
            break;
          }

          MPZ_CHECK_FORMAT (got);

          if (got_ret != want_ret)
            {
              printf ("%s wrong return value\n", name);
              error = 1;
            }
          if (want_ret == 1 && mpz_cmp (want, got) != 0)
            {
              printf ("%s wrong result\n", name);
              error = 1;
            }
          if (got_upto != want_upto)
            {
              printf ("%s wrong upto\n", name);
              error = 1;
            }
          if (got_ftell != -1 && want_ftell != -1 && got_ftell != want_ftell)
            {
              printf ("%s wrong ftell\n", name);
              error = 1;
            }
          if (error)
            {
              printf    ("  fmt   \"%s\"\n", data[i].fmt);
              printf    ("  input \"%s\"\n", data[i].input);
              printf    ("  ignore %d\n", ignore);
              printf    ("  ret   want=%d\n", want_ret);
              printf    ("        got =%d\n", got_ret);
              mpz_trace ("  value want", want);
              mpz_trace ("        got ", got);
              printf    ("  upto  want =%d\n", want_upto);
              printf    ("        got  =%d\n", got_upto);
              if (got_ftell != -1)
                {
                  printf    ("  ftell want =%ld\n", want_ftell);
                  printf    ("        got  =%ld\n", got_ftell);
                }
              abort ();
            }
        }
    }

  mpz_clear (got);
  mpz_clear (want);
}

void
check_q (void)
{
  static const struct {
    const char  *fmt;
    const char  *input;
    const char  *want;
    int         ret;
    long        ftell;

  } data[] = {

    { "%Qd",    "0",    "0", 1, -1 },
    { "%Qd",    "1",    "1", 1, -1 },
    { "%Qd",  "123",  "123", 1, -1 },
    { "%Qd",   "+0",    "0", 1, -1 },
    { "%Qd",   "+1",    "1", 1, -1 },
    { "%Qd", "+123",  "123", 1, -1 },
    { "%Qd",   "-0",    "0", 1, -1 },
    { "%Qd",   "-1",   "-1", 1, -1 },
    { "%Qd", "-123", "-123", 1, -1 },

    { "%Qo",    "0",    "0", 1, -1 },
    { "%Qo",  "173",  "123", 1, -1 },
    { "%Qo",   "+0",    "0", 1, -1 },
    { "%Qo", "+173",  "123", 1, -1 },
    { "%Qo",   "-0",    "0", 1, -1 },
    { "%Qo", "-173", "-123", 1, -1 },

    { "%Qx",    "0",    "0", 1, -1 },
    { "%Qx",   "7b",  "123", 1, -1 },
    { "%Qx",   "7b",  "123", 1, -1 },
    { "%Qx",   "+0",    "0", 1, -1 },
    { "%Qx",  "+7b",  "123", 1, -1 },
    { "%Qx",  "+7b",  "123", 1, -1 },
    { "%Qx",   "-0",   "-0", 1, -1 },
    { "%Qx",  "-7b", "-123", 1, -1 },
    { "%Qx",  "-7b", "-123", 1, -1 },
    { "%QX",    "0",    "0", 1, -1 },
    { "%QX",   "7b",  "123", 1, -1 },
    { "%QX",   "7b",  "123", 1, -1 },
    { "%QX",   "+0",    "0", 1, -1 },
    { "%QX",  "+7b",  "123", 1, -1 },
    { "%QX",  "+7b",  "123", 1, -1 },
    { "%QX",   "-0",   "-0", 1, -1 },
    { "%QX",  "-7b", "-123", 1, -1 },
    { "%QX",  "-7b", "-123", 1, -1 },
    { "%Qx",    "0",    "0", 1, -1 },
    { "%Qx",   "7B",  "123", 1, -1 },
    { "%Qx",   "7B",  "123", 1, -1 },
    { "%Qx",   "+0",    "0", 1, -1 },
    { "%Qx",  "+7B",  "123", 1, -1 },
    { "%Qx",  "+7B",  "123", 1, -1 },
    { "%Qx",   "-0",   "-0", 1, -1 },
    { "%Qx",  "-7B", "-123", 1, -1 },
    { "%Qx",  "-7B", "-123", 1, -1 },
    { "%QX",    "0",    "0", 1, -1 },
    { "%QX",   "7B",  "123", 1, -1 },
    { "%QX",   "7B",  "123", 1, -1 },
    { "%QX",   "+0",    "0", 1, -1 },
    { "%QX",  "+7B",  "123", 1, -1 },
    { "%QX",  "+7B",  "123", 1, -1 },
    { "%QX",   "-0",   "-0", 1, -1 },
    { "%QX",  "-7B", "-123", 1, -1 },
    { "%QX",  "-7B", "-123", 1, -1 },

    { "%Qi",    "0",    "0", 1, -1 },
    { "%Qi",    "1",    "1", 1, -1 },
    { "%Qi",  "123",  "123", 1, -1 },
    { "%Qi",   "+0",    "0", 1, -1 },
    { "%Qi",   "+1",    "1", 1, -1 },
    { "%Qi", "+123",  "123", 1, -1 },
    { "%Qi",   "-0",    "0", 1, -1 },
    { "%Qi",   "-1",   "-1", 1, -1 },
    { "%Qi", "-123", "-123", 1, -1 },

    { "%Qi",    "00",    "0", 1, -1 },
    { "%Qi",  "0173",  "123", 1, -1 },
    { "%Qi",   "+00",    "0", 1, -1 },
    { "%Qi", "+0173",  "123", 1, -1 },
    { "%Qi",   "-00",    "0", 1, -1 },
    { "%Qi", "-0173", "-123", 1, -1 },

    { "%Qi",    "0x0",    "0", 1, -1 },
    { "%Qi",   "0x7b",  "123", 1, -1 },
    { "%Qi",   "0x7b",  "123", 1, -1 },
    { "%Qi",   "+0x0",    "0", 1, -1 },
    { "%Qi",  "+0x7b",  "123", 1, -1 },
    { "%Qi",  "+0x7b",  "123", 1, -1 },
    { "%Qi",   "-0x0",   "-0", 1, -1 },
    { "%Qi",  "-0x7b", "-123", 1, -1 },
    { "%Qi",  "-0x7b", "-123", 1, -1 },
    { "%Qi",    "0X0",    "0", 1, -1 },
    { "%Qi",   "0X7b",  "123", 1, -1 },
    { "%Qi",   "0X7b",  "123", 1, -1 },
    { "%Qi",   "+0X0",    "0", 1, -1 },
    { "%Qi",  "+0X7b",  "123", 1, -1 },
    { "%Qi",  "+0X7b",  "123", 1, -1 },
    { "%Qi",   "-0X0",   "-0", 1, -1 },
    { "%Qi",  "-0X7b", "-123", 1, -1 },
    { "%Qi",  "-0X7b", "-123", 1, -1 },
    { "%Qi",    "0x0",    "0", 1, -1 },
    { "%Qi",   "0x7B",  "123", 1, -1 },
    { "%Qi",   "0x7B",  "123", 1, -1 },
    { "%Qi",   "+0x0",    "0", 1, -1 },
    { "%Qi",  "+0x7B",  "123", 1, -1 },
    { "%Qi",  "+0x7B",  "123", 1, -1 },
    { "%Qi",   "-0x0",   "-0", 1, -1 },
    { "%Qi",  "-0x7B", "-123", 1, -1 },
    { "%Qi",  "-0x7B", "-123", 1, -1 },
    { "%Qi",    "0X0",    "0", 1, -1 },
    { "%Qi",   "0X7B",  "123", 1, -1 },
    { "%Qi",   "0X7B",  "123", 1, -1 },
    { "%Qi",   "+0X0",    "0", 1, -1 },
    { "%Qi",  "+0X7B",  "123", 1, -1 },
    { "%Qi",  "+0X7B",  "123", 1, -1 },
    { "%Qi",   "-0X0",   "-0", 1, -1 },
    { "%Qi",  "-0X7B", "-123", 1, -1 },
    { "%Qi",  "-0X7B", "-123", 1, -1 },

    { "%Qd",    " 0",    "0", 1, -1 },
    { "%Qd",   "  0",    "0", 1, -1 },
    { "%Qd",  "   0",    "0", 1, -1 },
    { "%Qd",   "\t0",    "0", 1, -1 },
    { "%Qd", "\t\t0",    "0", 1, -1 },

    { "%Qd",  "3/2",   "3/2", 1, -1 },
    { "%Qd", "+3/2",   "3/2", 1, -1 },
    { "%Qd", "-3/2",  "-3/2", 1, -1 },

    { "%Qx",  "f/10", "15/16", 1, -1 },
    { "%Qx",  "F/10", "15/16", 1, -1 },
    { "%QX",  "f/10", "15/16", 1, -1 },
    { "%QX",  "F/10", "15/16", 1, -1 },

    { "%Qo",  "20/21",  "16/17", 1, -1 },
    { "%Qo", "-20/21", "-16/17", 1, -1 },

    { "%Qi",    "10/11",  "10/11", 1, -1 },
    { "%Qi",   "+10/11",  "10/11", 1, -1 },
    { "%Qi",   "-10/11", "-10/11", 1, -1 },
    { "%Qi",   "010/11",   "8/11", 1, -1 },
    { "%Qi",  "+010/11",   "8/11", 1, -1 },
    { "%Qi",  "-010/11",  "-8/11", 1, -1 },
    { "%Qi",  "0x10/11",  "16/11", 1, -1 },
    { "%Qi", "+0x10/11",  "16/11", 1, -1 },
    { "%Qi", "-0x10/11", "-16/11", 1, -1 },

    { "%Qi",    "10/011",  "10/9", 1, -1 },
    { "%Qi",   "+10/011",  "10/9", 1, -1 },
    { "%Qi",   "-10/011", "-10/9", 1, -1 },
    { "%Qi",   "010/011",   "8/9", 1, -1 },
    { "%Qi",  "+010/011",   "8/9", 1, -1 },
    { "%Qi",  "-010/011",  "-8/9", 1, -1 },
    { "%Qi",  "0x10/011",  "16/9", 1, -1 },
    { "%Qi", "+0x10/011",  "16/9", 1, -1 },
    { "%Qi", "-0x10/011", "-16/9", 1, -1 },

    { "%Qi",    "10/0x11",  "10/17", 1, -1 },
    { "%Qi",   "+10/0x11",  "10/17", 1, -1 },
    { "%Qi",   "-10/0x11", "-10/17", 1, -1 },
    { "%Qi",   "010/0x11",   "8/17", 1, -1 },
    { "%Qi",  "+010/0x11",   "8/17", 1, -1 },
    { "%Qi",  "-010/0x11",  "-8/17", 1, -1 },
    { "%Qi",  "0x10/0x11",  "16/17", 1, -1 },
    { "%Qi", "+0x10/0x11",  "16/17", 1, -1 },
    { "%Qi", "-0x10/0x11", "-16/17", 1, -1 },

    { "hello%Qd",      "hello0",         "0", 1, -1 },
    { "hello%Qd",      "hello 0",        "0", 1, -1 },
    { "hello%Qd",      "hello \t0",      "0", 1, -1 },
    { "hello%Qdworld", "hello 0world",   "0", 1, -1 },
    { "hello%Qd",      "hello3/2",     "3/2", 1, -1 },

    { "hello%*Qd",      "hello0",        "-999/121", 0, -1 },
    { "hello%*Qd",      "hello 0",       "-999/121", 0, -1 },
    { "hello%*Qd",      "hello \t0",     "-999/121", 0, -1 },
    { "hello%*Qdworld", "hello 0world",  "-999/121", 0, -1 },
    { "hello%*Qdworld", "hello3/2world", "-999/121", 0, -1 },

    { "%Qd",    "",     "-999/121", -1, -1 },
    { "%Qd",   " ",     "-999/121", -1, -1 },
    { " %Qd",   "",     "-999/121", -1, -1 },
    { "xyz%Qd", "",     "-999/121", -1, -1 },

    { "%*Qd",    "",     "-999/121", -1, -1 },
    { " %*Qd",   "",     "-999/121", -1, -1 },
    { "xyz%*Qd", "",     "-999/121", -1, -1 },

    /* match something, but invalid */
    { "%Qd",    "-",     "-999/121",  0, 1 },
    { "%Qd",    "+",     "-999/121",  0, 1 },
    { "%Qd",    "/-",    "-999/121",  0, 1 },
    { "%Qd",    "/+",    "-999/121",  0, 1 },
    { "%Qd",    "-/",    "-999/121",  0, 1 },
    { "%Qd",    "+/",    "-999/121",  0, 1 },
    { "%Qd",    "-/-",   "-999/121",  0, 1 },
    { "%Qd",    "-/+",   "-999/121",  0, 1 },
    { "%Qd",    "+/+",   "-999/121",  0, 1 },
    { "%Qd",    "/123",  "-999/121",  0, 1 },
    { "%Qd",    "-/123", "-999/121",  0, 1 },
    { "%Qd",    "+/123", "-999/121",  0, 1 },
    { "%Qd",    "123/",  "-999/121",  0, 1 },
    { "%Qd",    "123/-", "-999/121",  0, 1 },
    { "%Qd",    "123/+", "-999/121",  0, 1 },
    { "xyz%Qd", "xyz-",  "-999/121",  0, 4 },
    { "xyz%Qd", "xyz+",  "-999/121",  0, 4 },

    { "%1Qi",  "12/57", "1",        1, 1 },
    { "%2Qi",  "12/57", "12",       1, 2 },
    { "%3Qi",  "12/57", "-999/121", 0, -1 },
    { "%4Qi",  "12/57", "12/5",     1, 4 },
    { "%5Qi",  "12/57", "12/57",    1, 5 },
    { "%6Qi",  "12/57", "12/57",    1, 5 },
    { "%7Qi",  "12/57", "12/57",    1, 5 },

    { "%1Qi",  "012/057", "0",        1, 1 },
    { "%2Qi",  "012/057", "01",       1, 2 },
    { "%3Qi",  "012/057", "012",      1, 3 },
    { "%4Qi",  "012/057", "-999/121", 0, -1 },
    { "%5Qi",  "012/057", "012/0",    1, 5 },
    { "%6Qi",  "012/057", "012/5",    1, 6 },
    { "%7Qi",  "012/057", "012/057",  1, 7 },
    { "%8Qi",  "012/057", "012/057",  1, 7 },
    { "%9Qi",  "012/057", "012/057",  1, 7 },

    { "%1Qi",  "0x12/0x57", "0",         1, 1 },
    { "%2Qi",  "0x12/0x57", "-999",      0, 2 },
    { "%3Qi",  "0x12/0x57", "0x1",       1, 3 },
    { "%4Qi",  "0x12/0x57", "0x12",      1, 4 },
    { "%5Qi",  "0x12/0x57", "-999/121",  0, 5 },
    { "%6Qi",  "0x12/0x57", "0x12/0",    1, 6 },
    { "%7Qi",  "0x12/0x57", "-999/121",  0, 7 },
    { "%8Qi",  "0x12/0x57", "0x12/0x5",  1, 8 },
    { "%9Qi",  "0x12/0x57", "0x12/0x57", 1, 9 },
    { "%10Qi", "0x12/0x57", "0x12/0x57", 1, 9 },
    { "%11Qi", "0x12/0x57", "0x12/0x57", 1, 9 },

    { "%Qd",  "xyz", "0", 0, 0 },
  };

  int         i, j, ignore, got_ret, want_ret, got_upto, want_upto;
  mpq_t       got, want;
  long        got_l, want_ftell;
  int         error = 0;
  fun_t       fun;
  const char  *name;
  char        fmt[128];

  mpq_init (got);
  mpq_init (want);

  for (i = 0; i < numberof (data); i++)
    {
      mpq_set_str_or_abort (want, data[i].want, 0);

      ASSERT_ALWAYS (strlen (data[i].fmt) + 2 < sizeof (fmt));
      strcpy (fmt, data[i].fmt);
      strcat (fmt, "%n");

      ignore = (strchr (fmt, '*') != NULL);

      for (j = 0; j <= 3; j++)
        {
          want_ret = data[i].ret;

          want_ftell = data[i].ftell;
          if (want_ftell == -1)
            want_ftell = strlen (data[i].input);
          want_upto = want_ftell;

          if (want_ret == -1 || (want_ret == 0 && ! ignore))
            {
              want_ftell = -1;
              want_upto = -555;
            }

          switch (j) {
          case 0:
            name = "gmp_sscanf";
            fun = fun_gmp_sscanf;
            break;
          case 1:
            name = "gmp_fscanf";
            fun = fun_gmp_fscanf;
            break;
          case 2:
            if (strchr (data[i].input, '/') != NULL)
              continue;
            if (! libc_scanf_convert (fmt))
              continue;
            name = "standard sscanf";
            fun = fun_sscanf;
            break;
          case 3:
            if (strchr (data[i].input, '/') != NULL)
              continue;
            if (! libc_scanf_convert (fmt))
              continue;
            name = "standard fscanf";
            fun = fun_fscanf;
            break;
          default:
            ASSERT_ALWAYS (0);
            break;
          }

          got_upto = -555;
          got_ftell = -1;

          switch (j) {
          case 0:
          case 1:
            mpq_set_si (got, -999L, 121L);
            if (ignore)
              got_ret = (*fun) (data[i].input, fmt, &got_upto, NULL);
            else
              got_ret = (*fun) (data[i].input, fmt, got, &got_upto);
            break;
          case 2:
          case 3:
            got_l = -999L;
            if (ignore)
              got_ret = (*fun) (data[i].input, fmt, &got_upto, NULL);
            else
              got_ret = (*fun) (data[i].input, fmt, &got_l, &got_upto);
            mpq_set_si (got, got_l, (got_l == -999L ? 121L : 1L));
            break;
          default:
            ASSERT_ALWAYS (0);
            break;
          }

          MPZ_CHECK_FORMAT (mpq_numref (got));
          MPZ_CHECK_FORMAT (mpq_denref (got));

          if (got_ret != want_ret)
            {
              printf ("%s wrong return value\n", name);
              error = 1;
            }
          /* use direct mpz compares, since some of the test data is
             non-canonical and can trip ASSERTs in mpq_equal */
          if (want_ret == 1
              && ! (mpz_cmp (mpq_numref(want), mpq_numref(got)) == 0
                    && mpz_cmp (mpq_denref(want), mpq_denref(got)) == 0))
            {
              printf ("%s wrong result\n", name);
              error = 1;
            }
          if (got_upto != want_upto)
            {
              printf ("%s wrong upto\n", name);
              error = 1;
            }
          if (got_ftell != -1 && want_ftell != -1 && got_ftell != want_ftell)
            {
              printf ("%s wrong ftell\n", name);
              error = 1;
            }
          if (error)
            {
              printf    ("  fmt   \"%s\"\n", data[i].fmt);
              printf    ("  input \"%s\"\n", data[i].input);
              printf    ("  ret   want=%d\n", want_ret);
              printf    ("        got =%d\n", got_ret);
              mpq_trace ("  value want", want);
              mpq_trace ("        got ", got);
              printf    ("  upto  want=%d\n", want_upto);
              printf    ("        got =%d\n", got_upto);
              if (got_ftell != -1)
                {
                  printf    ("  ftell want =%ld\n", want_ftell);
                  printf    ("        got  =%ld\n", got_ftell);
                }
              abort ();
            }
        }
    }

  mpq_clear (got);
  mpq_clear (want);
}

void
check_f (void)
{
  static const struct {
    const char  *fmt;
    const char  *input;
    const char  *want;
    int         ret;
    long        ftell;    /* or -1 for length of input string */

  } data[] = {

    { "%Ff",    "0",    "0", 1, -1 },
    { "%Fe",    "0",    "0", 1, -1 },
    { "%FE",    "0",    "0", 1, -1 },
    { "%Fg",    "0",    "0", 1, -1 },
    { "%FG",    "0",    "0", 1, -1 },

    { "%Ff",  "123",    "123", 1, -1 },
    { "%Ff", "+123",    "123", 1, -1 },
    { "%Ff", "-123",   "-123", 1, -1 },
    { "%Ff",  "123.",   "123", 1, -1 },
    { "%Ff", "+123.",   "123", 1, -1 },
    { "%Ff", "-123.",  "-123", 1, -1 },
    { "%Ff",  "123.0",  "123", 1, -1 },
    { "%Ff", "+123.0",  "123", 1, -1 },
    { "%Ff", "-123.0", "-123", 1, -1 },
    { "%Ff",  "0123",   "123", 1, -1 },
    { "%Ff", "-0123",  "-123", 1, -1 },

    { "%Ff",  "123.456e3",   "123456", 1, -1 },
    { "%Ff", "-123.456e3",  "-123456", 1, -1 },
    { "%Ff",  "123.456e+3",  "123456", 1, -1 },
    { "%Ff", "-123.456e+3", "-123456", 1, -1 },
    { "%Ff",  "123000e-3",      "123", 1, -1 },
    { "%Ff", "-123000e-3",     "-123", 1, -1 },
    { "%Ff",  "123000.e-3",     "123", 1, -1 },
    { "%Ff", "-123000.e-3",    "-123", 1, -1 },

    { "%Ff",  "123.456E3",   "123456", 1, -1 },
    { "%Ff", "-123.456E3",  "-123456", 1, -1 },
    { "%Ff",  "123.456E+3",  "123456", 1, -1 },
    { "%Ff", "-123.456E+3", "-123456", 1, -1 },
    { "%Ff",  "123000E-3",      "123", 1, -1 },
    { "%Ff", "-123000E-3",     "-123", 1, -1 },
    { "%Ff",  "123000.E-3",     "123", 1, -1 },
    { "%Ff", "-123000.E-3",    "-123", 1, -1 },

    { "%Ff",  ".456e3",   "456", 1, -1 },
    { "%Ff", "-.456e3",  "-456", 1, -1 },
    { "%Ff",  ".456e+3",  "456", 1, -1 },
    { "%Ff", "-.456e+3", "-456", 1, -1 },

    { "%Ff",    " 0",    "0", 1, -1 },
    { "%Ff",   "  0",    "0", 1, -1 },
    { "%Ff",  "   0",    "0", 1, -1 },
    { "%Ff",   "\t0",    "0", 1, -1 },
    { "%Ff", "\t\t0",    "0", 1, -1 },

    { "hello%Fg",      "hello0",       "0",   1, -1 },
    { "hello%Fg",      "hello 0",      "0",   1, -1 },
    { "hello%Fg",      "hello \t0",    "0",   1, -1 },
    { "hello%Fgworld", "hello 0world", "0",   1, -1 },
    { "hello%Fg",      "hello3.0",     "3.0", 1, -1 },

    { "hello%*Fg",      "hello0",        "-999", 0, -1 },
    { "hello%*Fg",      "hello 0",       "-999", 0, -1 },
    { "hello%*Fg",      "hello \t0",     "-999", 0, -1 },
    { "hello%*Fgworld", "hello 0world",  "-999", 0, -1 },
    { "hello%*Fgworld", "hello3.0world", "-999", 0, -1 },

    { "%Ff",     "",   "-999", -1, -1 },
    { "%Ff",    " ",   "-999", -1, -1 },
    { "%Ff",   "\t",   "-999", -1, -1 },
    { "%Ff",  " \t",   "-999", -1, -1 },
    { " %Ff",    "",   "-999", -1, -1 },
    { "xyz%Ff",  "",   "-999", -1, -1 },

    { "%*Ff",    "",   "-999", -1, -1 },
    { " %*Ff",   "",   "-999", -1, -1 },
    { "xyz%*Ff", "",   "-999", -1, -1 },

    { "%Ff",    "xyz", "0", 0 },

    /* various non-empty but invalid */
    { "%Ff",    "-",      "-999",  0, 1 },
    { "%Ff",    "+",      "-999",  0, 1 },
    { "xyz%Ff", "xyz-",   "-999",  0, 4 },
    { "xyz%Ff", "xyz+",   "-999",  0, 4 },
    { "%Ff",    "-.",     "-999",  0, 2 },
    { "%Ff",    "+.",     "-999",  0, 2 },
    { "%Ff",    ".e",     "-999",  0, 1 },
    { "%Ff",   "-.e",     "-999",  0, 2 },
    { "%Ff",   "+.e",     "-999",  0, 2 },
    { "%Ff",    ".E",     "-999",  0, 1 },
    { "%Ff",   "-.E",     "-999",  0, 2 },
    { "%Ff",   "+.E",     "-999",  0, 2 },
    { "%Ff",    ".e123",  "-999",  0, 1 },
    { "%Ff",   "-.e123",  "-999",  0, 2 },
    { "%Ff",   "+.e123",  "-999",  0, 2 },
    { "%Ff",    "123e",   "-999",  0, 4 },
    { "%Ff",   "-123e",   "-999",  0, 5 },
    { "%Ff",    "123e-",  "-999",  0, 5 },
    { "%Ff",   "-123e-",  "-999",  0, 6 },
    { "%Ff",    "123e+",  "-999",  0, 5 },
    { "%Ff",   "-123e+",  "-999",  0, 6 },
    { "%Ff",   "123e-Z",  "-999",  0, 5 },

    /* hex floats */
    { "%Ff", "0x123p0",       "291",  1, -1 },
    { "%Ff", "0x123P0",       "291",  1, -1 },
    { "%Ff", "0X123p0",       "291",  1, -1 },
    { "%Ff", "0X123P0",       "291",  1, -1 },
    { "%Ff", "-0x123p0",     "-291",  1, -1 },
    { "%Ff", "+0x123p0",      "291",  1, -1 },
    { "%Ff", "0x123.p0",      "291",  1, -1 },
    { "%Ff", "0x12.3p4",      "291",  1, -1 },
    { "%Ff", "-0x12.3p4",    "-291",  1, -1 },
    { "%Ff", "+0x12.3p4",     "291",  1, -1 },
    { "%Ff", "0x1230p-4",     "291",  1, -1 },
    { "%Ff", "-0x1230p-4",   "-291",  1, -1 },
    { "%Ff", "+0x1230p-4",    "291",  1, -1 },
    { "%Ff", "+0x.1230p12",   "291",  1, -1 },
    { "%Ff", "+0x123000p-12", "291",  1, -1 },
    { "%Ff", "0x123 p12",     "291",  1, 5 },
    { "%Ff", "0x9 9",           "9",  1, 3 },
    { "%Ff", "0x01",            "1",  1, 4 },
    { "%Ff", "0x23",           "35",  1, 4 },
    { "%Ff", "0x45",           "69",  1, 4 },
    { "%Ff", "0x67",          "103",  1, 4 },
    { "%Ff", "0x89",          "137",  1, 4 },
    { "%Ff", "0xAB",          "171",  1, 4 },
    { "%Ff", "0xCD",          "205",  1, 4 },
    { "%Ff", "0xEF",          "239",  1, 4 },
    { "%Ff", "0xab",          "171",  1, 4 },
    { "%Ff", "0xcd",          "205",  1, 4 },
    { "%Ff", "0xef",          "239",  1, 4 },
    { "%Ff", "0x100p0A",      "256",  1, 7 },
    { "%Ff", "0x1p9",         "512",  1, -1 },

    /* invalid hex floats */
    { "%Ff", "0x",     "-999",  0, 2 },
    { "%Ff", "-0x",    "-999",  0, 3 },
    { "%Ff", "+0x",    "-999",  0, 3 },
    { "%Ff", "0x-",    "-999",  0, 2 },
    { "%Ff", "0x+",    "-999",  0, 2 },
    { "%Ff", "0x.",    "-999",  0, 3 },
    { "%Ff", "-0x.",   "-999",  0, 4 },
    { "%Ff", "+0x.",   "-999",  0, 4 },
    { "%Ff", "0x.p",   "-999",  0, 3 },
    { "%Ff", "-0x.p",  "-999",  0, 4 },
    { "%Ff", "+0x.p",  "-999",  0, 4 },
    { "%Ff", "0x.P",   "-999",  0, 3 },
    { "%Ff", "-0x.P",  "-999",  0, 4 },
    { "%Ff", "+0x.P",  "-999",  0, 4 },
    { "%Ff", ".p123",  "-999",  0, 1 },
    { "%Ff", "-.p123", "-999",  0, 2 },
    { "%Ff", "+.p123", "-999",  0, 2 },
    { "%Ff", "0x1p",   "-999",  0, 4 },
    { "%Ff", "0x1p-",  "-999",  0, 5 },
    { "%Ff", "0x1p+",  "-999",  0, 5 },
    { "%Ff", "0x123p 12", "291",  0, 6 },
    { "%Ff", "0x 123p12", "291",  0, 2 },

  };

  int         i, j, ignore, got_ret, want_ret, got_upto, want_upto;
  mpf_t       got, want;
  double      got_d;
  long        want_ftell;
  int         error = 0;
  fun_t       fun;
  const char  *name;
  char        fmt[128];

  mpf_init (got);
  mpf_init (want);

  for (i = 0; i < numberof (data); i++)
    {
      mpf_set_str_or_abort (want, data[i].want, 10);

      ASSERT_ALWAYS (strlen (data[i].fmt) + 2 < sizeof (fmt));
      strcpy (fmt, data[i].fmt);
      strcat (fmt, "%n");

      ignore = (strchr (fmt, '*') != NULL);

      for (j = 0; j <= 3; j++)
        {
          want_ret = data[i].ret;

          want_ftell = data[i].ftell;
          if (want_ftell == -1)
            want_ftell = strlen (data[i].input);
          want_upto = want_ftell;

          if (want_ret == -1 || (want_ret == 0 && ! ignore))
            want_upto = -555;

          switch (j) {
          case 0:
            name = "gmp_sscanf";
            fun = fun_gmp_sscanf;
            break;
          case 1:
            name = "gmp_fscanf";
            fun = fun_gmp_fscanf;
            break;
          case 2:
            if (! libc_scanf_convert (fmt))
              continue;
            name = "standard sscanf";
            fun = fun_sscanf;
            break;
          case 3:
            if (! libc_scanf_convert (fmt))
              continue;
            name = "standard fscanf";
            fun = fun_fscanf;
            break;
          default:
            ASSERT_ALWAYS (0);
            break;
          }

          got_upto = -555;
          got_ftell = -1;

          switch (j) {
          case 0:
          case 1:
            mpf_set_si (got, -999L);
            if (ignore)
              got_ret = (*fun) (data[i].input, fmt, &got_upto, NULL);
            else
              got_ret = (*fun) (data[i].input, fmt, got, &got_upto);
            break;
          case 2:
          case 3:
            got_d = -999L;
            if (ignore)
              got_ret = (*fun) (data[i].input, fmt, &got_upto, NULL);
            else
              got_ret = (*fun) (data[i].input, fmt, &got_d, &got_upto);
            mpf_set_d (got, got_d);
            break;
          default:
            ASSERT_ALWAYS (0);
            break;
          }

          MPF_CHECK_FORMAT (got);

          if (got_ret != want_ret)
            {
              printf ("%s wrong return value\n", name);
              error = 1;
            }
          if (want_ret == 1 && mpf_cmp (want, got) != 0)
            {
              printf ("%s wrong result\n", name);
              error = 1;
            }
          if (got_upto != want_upto)
            {
              printf ("%s wrong upto\n", name);
              error = 1;
            }
          if (got_ftell != -1 && want_ftell != -1 && got_ftell != want_ftell)
            {
              printf ("%s wrong ftell\n", name);
              error = 1;
            }
          if (error)
            {
              printf    ("  fmt   \"%s\"\n", data[i].fmt);
              printf    ("  input \"%s\"\n", data[i].input);
              printf    ("  ret   want=%d\n", want_ret);
              printf    ("        got =%d\n", got_ret);
              mpf_trace ("  value want", want);
              mpf_trace ("        got ", got);
              printf    ("  upto  want=%d\n", want_upto);
              printf    ("        got =%d\n", got_upto);
              if (got_ftell != -1)
                {
                  printf    ("  ftell want =%ld\n", want_ftell);
                  printf    ("        got  =%ld\n", got_ftell);
                }
              abort ();
            }
        }
    }

  mpf_clear (got);
  mpf_clear (want);
}


void
check_n (void)
{
  int    ret;

  /* %n suppressed */
  {
    int n = 123;
    gmp_sscanf ("   ", " %*n", &n);
    ASSERT_ALWAYS (n == 123);
  }
  {
    int n = 123;
    fromstring_gmp_fscanf ("   ", " %*n", &n);
    ASSERT_ALWAYS (n == 123);
  }


#define CHECK_N(type, string)                           \
  do {                                                  \
    type  x[2];                                         \
    char  fmt[128];                                     \
    int   ret;                                          \
                                                        \
    x[0] = ~ (type) 0;                                  \
    x[1] = ~ (type) 0;                                  \
    sprintf (fmt, "abc%%%sn", string);                  \
    ret = gmp_sscanf ("abc", fmt, &x[0]);               \
                                                        \
    ASSERT_ALWAYS (ret == 0);                           \
                                                        \
    /* should write whole of x[0] and none of x[1] */   \
    ASSERT_ALWAYS (x[0] == 3);                          \
    ASSERT_ALWAYS (x[1] == (type) ~ (type) 0);		\
                                                        \
  } while (0)

  CHECK_N (char,      "hh");
  CHECK_N (long,      "l");
#if HAVE_LONG_LONG
  CHECK_N (long long, "L");
#endif
#if HAVE_INTMAX_T
  CHECK_N (intmax_t,  "j");
#endif
#if HAVE_PTRDIFF_T
  CHECK_N (ptrdiff_t, "t");
#endif
  CHECK_N (short,     "h");
  CHECK_N (size_t,    "z");

  /* %Zn */
  {
    mpz_t  x[2];
    mpz_init_set_si (x[0], -987L);
    mpz_init_set_si (x[1],  654L);
    ret = gmp_sscanf ("xyz   ", "xyz%Zn", x[0]);
    MPZ_CHECK_FORMAT (x[0]);
    MPZ_CHECK_FORMAT (x[1]);
    ASSERT_ALWAYS (ret == 0);
    ASSERT_ALWAYS (mpz_cmp_ui (x[0], 3L) == 0);
    ASSERT_ALWAYS (mpz_cmp_ui (x[1], 654L) == 0);
    mpz_clear (x[0]);
    mpz_clear (x[1]);
  }
  {
    mpz_t  x;
    mpz_init (x);
    ret = fromstring_gmp_fscanf ("xyz   ", "xyz%Zn", x);
    ASSERT_ALWAYS (ret == 0);
    ASSERT_ALWAYS (mpz_cmp_ui (x, 3L) == 0);
    mpz_clear (x);
  }

  /* %Qn */
  {
    mpq_t  x[2];
    mpq_init (x[0]);
    mpq_init (x[1]);
    mpq_set_ui (x[0], 987L, 654L);
    mpq_set_ui (x[1], 4115L, 226L);
    ret = gmp_sscanf ("xyz   ", "xyz%Qn", x[0]);
    MPQ_CHECK_FORMAT (x[0]);
    MPQ_CHECK_FORMAT (x[1]);
    ASSERT_ALWAYS (ret == 0);
    ASSERT_ALWAYS (mpq_cmp_ui (x[0], 3L, 1L) == 0);
    ASSERT_ALWAYS (mpq_cmp_ui (x[1], 4115L, 226L) == 0);
    mpq_clear (x[0]);
    mpq_clear (x[1]);
  }
  {
    mpq_t  x;
    mpq_init (x);
    ret = fromstring_gmp_fscanf ("xyz   ", "xyz%Qn", x);
    ASSERT_ALWAYS (ret == 0);
    ASSERT_ALWAYS (mpq_cmp_ui (x, 3L, 1L) == 0);
    mpq_clear (x);
  }

  /* %Fn */
  {
    mpf_t  x[2];
    mpf_init (x[0]);
    mpf_init (x[1]);
    mpf_set_ui (x[0], 987L);
    mpf_set_ui (x[1], 654L);
    ret = gmp_sscanf ("xyz   ", "xyz%Fn", x[0]);
    MPF_CHECK_FORMAT (x[0]);
    MPF_CHECK_FORMAT (x[1]);
    ASSERT_ALWAYS (ret == 0);
    ASSERT_ALWAYS (mpf_cmp_ui (x[0], 3L) == 0);
    ASSERT_ALWAYS (mpf_cmp_ui (x[1], 654L) == 0);
    mpf_clear (x[0]);
    mpf_clear (x[1]);
  }
  {
    mpf_t  x;
    mpf_init (x);
    ret = fromstring_gmp_fscanf ("xyz   ", "xyz%Fn", x);
    ASSERT_ALWAYS (ret == 0);
    ASSERT_ALWAYS (mpf_cmp_ui (x, 3L) == 0);
    mpf_clear (x);
  }
}


void
check_misc (void)
{
  int  ret, cmp;
  {
    int  a=9, b=8, c=7, n=66;
    mpz_t  z;
    mpz_init (z);
    ret = gmp_sscanf ("1 2 3 4", "%d %d %d %Zd%n",
                      &a, &b, &c, z, &n);
    ASSERT_ALWAYS (ret == 4);
    ASSERT_ALWAYS (a == 1);
    ASSERT_ALWAYS (b == 2);
    ASSERT_ALWAYS (c == 3);
    ASSERT_ALWAYS (n == 7);
    ASSERT_ALWAYS (mpz_cmp_ui (z, 4L) == 0);
    mpz_clear (z);
  }
  {
    int  a=9, b=8, c=7, n=66;
    mpz_t  z;
    mpz_init (z);
    ret = fromstring_gmp_fscanf ("1 2 3 4", "%d %d %d %Zd%n",
                                 &a, &b, &c, z, &n);
    ASSERT_ALWAYS (ret == 4);
    ASSERT_ALWAYS (a == 1);
    ASSERT_ALWAYS (b == 2);
    ASSERT_ALWAYS (c == 3);
    ASSERT_ALWAYS (mpz_cmp_ui (z, 4L) == 0);
    ASSERT_ALWAYS (n == 7);
    ASSERT_ALWAYS (got_ftell == 7);
    mpz_clear (z);
  }

  {
    int  a=9, n=8;
    mpz_t  z;
    mpz_init (z);
    ret = gmp_sscanf ("1 2 3 4", "%d %*d %*d %Zd%n", &a, z, &n);
    ASSERT_ALWAYS (ret == 2);
    ASSERT_ALWAYS (a == 1);
    ASSERT_ALWAYS (mpz_cmp_ui (z, 4L) == 0);
    ASSERT_ALWAYS (n == 7);
    mpz_clear (z);
  }
  {
    int  a=9, n=8;
    mpz_t  z;
    mpz_init (z);
    ret = fromstring_gmp_fscanf ("1 2 3 4", "%d %*d %*d %Zd%n",
                                 &a, z, &n);
    ASSERT_ALWAYS (ret == 2);
    ASSERT_ALWAYS (a == 1);
    ASSERT_ALWAYS (mpz_cmp_ui (z, 4L) == 0);
    ASSERT_ALWAYS (n == 7);
    ASSERT_ALWAYS (got_ftell == 7);
    mpz_clear (z);
  }

  /* EOF for no matching */
  {
    char buf[128];
    ret = gmp_sscanf ("   ", "%s", buf);
    ASSERT_ALWAYS (ret == EOF);
    ret = fromstring_gmp_fscanf ("   ", "%s", buf);
    ASSERT_ALWAYS (ret == EOF);
    if (option_libc_scanf)
      {
        ret = sscanf ("   ", "%s", buf);
        ASSERT_ALWAYS (ret == EOF);
        ret = fun_fscanf ("   ", "%s", buf, NULL);
        ASSERT_ALWAYS (ret == EOF);
      }
  }

  /* suppressed field, then eof */
  {
    int  x;
    if (test_sscanf_eof_ok ())
      {
        ret = gmp_sscanf ("123", "%*d%d", &x);
        ASSERT_ALWAYS (ret == EOF);
      }
    ret = fromstring_gmp_fscanf ("123", "%*d%d", &x);
    ASSERT_ALWAYS (ret == EOF);
    if (option_libc_scanf)
      {
        ret = sscanf ("123", "%*d%d", &x);
        ASSERT_ALWAYS (ret == EOF);
        ret = fun_fscanf ("123", "%*d%d", &x, NULL);
        ASSERT_ALWAYS (ret == EOF);
      }
  }
  {
    mpz_t  x;
    mpz_init (x);
    ret = gmp_sscanf ("123", "%*Zd%Zd", x);
    ASSERT_ALWAYS (ret == EOF);
    ret = fromstring_gmp_fscanf ("123", "%*Zd%Zd", x);
    ASSERT_ALWAYS (ret == EOF);
    mpz_clear (x);
  }

  /* %[...], glibc only */
#ifdef __GLIBC__
  {
    char  buf[128];
    int   n = -1;
    buf[0] = '\0';
    ret = gmp_sscanf ("abcdefgh", "%[a-d]ef%n", buf, &n);
    ASSERT_ALWAYS (ret == 1);
    cmp = strcmp (buf, "abcd");
    ASSERT_ALWAYS (cmp == 0);
    ASSERT_ALWAYS (n == 6);
  }
  {
    char  buf[128];
    int   n = -1;
    buf[0] = '\0';
    ret = gmp_sscanf ("xyza", "%[^a]a%n", buf, &n);
    ASSERT_ALWAYS (ret == 1);
    cmp = strcmp (buf, "xyz");
    ASSERT_ALWAYS (cmp == 0);
    ASSERT_ALWAYS (n == 4);
  }
  {
    char  buf[128];
    int   n = -1;
    buf[0] = '\0';
    ret = gmp_sscanf ("ab]ab]", "%[]ab]%n", buf, &n);
    ASSERT_ALWAYS (ret == 1);
    cmp = strcmp (buf, "ab]ab]");
    ASSERT_ALWAYS (cmp == 0);
    ASSERT_ALWAYS (n == 6);
  }
  {
    char  buf[128];
    int   n = -1;
    buf[0] = '\0';
    ret = gmp_sscanf ("xyzb", "%[^]ab]b%n", buf, &n);
    ASSERT_ALWAYS (ret == 1);
    cmp = strcmp (buf, "xyz");
    ASSERT_ALWAYS (cmp == 0);
    ASSERT_ALWAYS (n == 4);
  }
#endif

  /* %zd etc won't be accepted by sscanf on old systems, and running
     something to see if they work might be bad, so only try it on glibc,
     and only on a new enough version (glibc 2.0 doesn't have %zd) */
#if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ > 0)
  {
    mpz_t   z;
    size_t  s = -1;
    mpz_init (z);
    ret = gmp_sscanf ("456 789", "%zd %Zd", &s, z);
    ASSERT_ALWAYS (ret == 2);
    ASSERT_ALWAYS (s == 456);
    ASSERT_ALWAYS (mpz_cmp_ui (z, 789L) == 0);
    mpz_clear (z);
  }
  {
    mpz_t      z;
    ptrdiff_t  d = -1;
    mpz_init (z);
    ret = gmp_sscanf ("456 789", "%td %Zd", &d, z);
    ASSERT_ALWAYS (ret == 2);
    ASSERT_ALWAYS (d == 456);
    ASSERT_ALWAYS (mpz_cmp_ui (z, 789L) == 0);
    mpz_clear (z);
  }
  {
    mpz_t      z;
    long long  ll = -1;
    mpz_init (z);
    ret = gmp_sscanf ("456 789", "%Ld %Zd", &ll, z);
    ASSERT_ALWAYS (ret == 2);
    ASSERT_ALWAYS (ll == 456);
    ASSERT_ALWAYS (mpz_cmp_ui (z, 789L) == 0);
    mpz_clear (z);
  }
#endif
}

int
main (int argc, char *argv[])
{
  if (argc > 1 && strcmp (argv[1], "-s") == 0)
    option_libc_scanf = 1;

  tests_start ();

  mp_trace_base = 16;

  check_z ();
  check_q ();
  check_f ();
  check_n ();
  check_misc ();

  unlink (TEMPFILE);
  tests_end ();
  exit (0);
}