format: Make format settings structure smaller and cheaper.
authorBen Pfaff <blp@cs.stanford.edu>
Wed, 6 Jan 2021 23:07:19 +0000 (15:07 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Wed, 6 Jan 2021 23:07:19 +0000 (15:07 -0800)
Until now, there has only been one copy of the fmt_settings structure in a
given PSPP process, so it hardly mattered what size it was.  In an upcoming
commit, there will be one copy per pivot table, so it becomes more
important.  This commit shrinks it.

Smake
src/data/format.c
src/data/format.h
src/data/settings.c

diff --git a/Smake b/Smake
index 3cf37f5df12a5e59bf5631a07c22b60b113c9dc0..0b64221ca2c16358f80b1c2cef5b94e5e9e9ed4a 100644 (file)
--- a/Smake
+++ b/Smake
@@ -136,6 +136,7 @@ GNULIB_MODULES = \
        unitypes \
        unlocked-io \
        vasprintf-posix \
+       verify \
        version-etc \
        version-etc-fsf \
        vfprintf-posix \
index 113d52592eea46f5593e598bd06693633eaa3510..8aa16e65fffc63f045a4d60e205a59dd9bb2536b 100644 (file)
 #include "gl/c-strcase.h"
 #include "gl/minmax.h"
 #include "gl/xalloc.h"
+#include "gl/xmemdup0.h"
 
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
 
-struct fmt_settings
-  {
-    struct fmt_number_style styles[FMT_NUMBER_OF_FORMATS];
-  };
-
 bool is_fmt_type (enum fmt_type);
 
 static bool valid_width (enum fmt_type, int width, enum fmt_use);
@@ -53,56 +49,39 @@ static int max_digits_for_bytes (int bytes);
 static void fmt_clamp_width (struct fmt_spec *, enum fmt_use);
 static void fmt_clamp_decimals (struct fmt_spec *, enum fmt_use);
 
-static void fmt_affix_set (struct fmt_affix *, const char *);
-static void fmt_affix_free (struct fmt_affix *);
-
-static void fmt_number_style_init (struct fmt_number_style *);
-static void fmt_number_style_clone (struct fmt_number_style *,
-                                    const struct fmt_number_style *);
-static void fmt_number_style_destroy (struct fmt_number_style *);
-
-/* Creates and returns a new struct fmt_settings with default format styles. */
-struct fmt_settings *
-fmt_settings_create (void)
+void
+fmt_settings_init (struct fmt_settings *settings)
 {
-  struct fmt_settings *settings;
-  int t;
-
-  settings = xzalloc (sizeof *settings);
-  for (t = 0 ; t < FMT_NUMBER_OF_FORMATS ; ++t)
-    fmt_number_style_init (&settings->styles[t]);
-  fmt_settings_set_decimal (settings, '.');
-
-  return settings;
+  *settings = (struct fmt_settings) FMT_SETTINGS_INIT;
 }
 
-/* Destroys SETTINGS. */
 void
-fmt_settings_destroy (struct fmt_settings *settings)
+fmt_settings_uninit (struct fmt_settings *settings)
 {
-  if (settings != NULL)
-    {
-      int t;
-
-      for (t = 0 ; t < FMT_NUMBER_OF_FORMATS ; ++t)
-        fmt_number_style_destroy (&settings->styles[t]);
-
-      free (settings->styles);
-    }
+  for (int i = 0; i < FMT_N_CCS; i++)
+    fmt_number_style_destroy (settings->ccs[i]);
 }
 
-/* Returns a copy of SETTINGS. */
-struct fmt_settings *
-fmt_settings_clone (const struct fmt_settings *old)
+void
+fmt_settings_copy (struct fmt_settings *new, const struct fmt_settings *old)
 {
-  struct fmt_settings *new;
-  int t;
-
-  new = xmalloc (sizeof *new);
-  for (t = 0 ; t < FMT_NUMBER_OF_FORMATS ; ++t)
-    fmt_number_style_clone (&new->styles[t], &old->styles[t]);
+  new->decimal = old->decimal;
+  for (int i = 0; i < FMT_N_CCS; i++)
+    new->ccs[i] = fmt_number_style_clone (old->ccs[i]);
+}
 
-  return new;
+static size_t
+fmt_type_to_cc_index (enum fmt_type type)
+{
+  switch (type)
+    {
+    case FMT_CCA: return 0;
+    case FMT_CCB: return 1;
+    case FMT_CCC: return 2;
+    case FMT_CCD: return 3;
+    case FMT_CCE: return 4;
+    default: NOT_REACHED ();
+    }
 }
 
 /* Returns the number formatting style associated with the given
@@ -111,59 +90,74 @@ const struct fmt_number_style *
 fmt_settings_get_style (const struct fmt_settings *settings,
                         enum fmt_type type)
 {
-  assert (is_fmt_type (type));
-  return &settings->styles[type];
-}
+  verify (FMT_F < 6);
+  verify (FMT_COMMA < 6);
+  verify (FMT_DOT < 6);
+  verify (FMT_DOLLAR < 6);
+  verify (FMT_PCT < 6);
+  verify (FMT_E < 6);
+
+#define OPPOSITE(C) ((C) == ',' ? '.' : ',')
+#define AFFIX(S) { .s = (char *) (S), .width = sizeof (S) - 1 }
+#define NS(PREFIX, SUFFIX, DECIMAL, GROUPING) { \
+    .neg_prefix = AFFIX ("-"),                  \
+    .prefix = AFFIX (PREFIX),                   \
+    .suffix = AFFIX (SUFFIX),                   \
+    .neg_suffix = AFFIX (""),                   \
+    .decimal = DECIMAL,                         \
+    .grouping = GROUPING,                       \
+  }
+#define ANS(DECIMAL, GROUPING) {                        \
+    [FMT_F]      = NS( "",  "", DECIMAL, 0),            \
+    [FMT_E]      = NS( "",  "", DECIMAL, 0),            \
+    [FMT_COMMA]  = NS( "",  "", DECIMAL, GROUPING),     \
+    [FMT_DOT]    = NS( "",  "", GROUPING, DECIMAL),     \
+    [FMT_DOLLAR] = NS("$",  "", DECIMAL, GROUPING),     \
+    [FMT_PCT]    = NS( "", "%", DECIMAL, 0),            \
+  }
+
+  static const struct fmt_number_style period_styles[6] = ANS ('.', ',');
+  static const struct fmt_number_style comma_styles[6] = ANS (',', '.');
+  static const struct fmt_number_style default_style = NS ("", "", '.', 0);
 
-/* Sets the number style for TYPE to have the given DECIMAL and GROUPING
-   characters, negative prefix NEG_PREFIX, prefix PREFIX, suffix SUFFIX, and
-   negative suffix NEG_SUFFIX.  All of the strings are UTF-8 encoded. */
-void
-fmt_settings_set_style (struct fmt_settings *settings, enum fmt_type type,
-                        char decimal, char grouping,
-                        const char *neg_prefix, const char *prefix,
-                        const char *suffix, const char *neg_suffix)
-{
-  struct fmt_number_style *style = &settings->styles[type];
-  int total_bytes, total_width;
-
-  assert (grouping == '.' || grouping == ',' || grouping == 0);
-  assert (decimal == '.' || decimal == ',');
-  assert (decimal != grouping);
-
-  fmt_number_style_destroy (style);
-
-  fmt_affix_set (&style->neg_prefix, neg_prefix);
-  fmt_affix_set (&style->prefix, prefix);
-  fmt_affix_set (&style->suffix, suffix);
-  fmt_affix_set (&style->neg_suffix, neg_suffix);
-  style->decimal = decimal;
-  style->grouping = grouping;
-
-  total_bytes = (strlen (neg_prefix) + strlen (prefix)
-                 + strlen (suffix) + strlen (neg_suffix));
-  total_width = (style->neg_prefix.width + style->prefix.width
-                 + style->suffix.width + style->neg_suffix.width);
-  style->extra_bytes = MAX (0, total_bytes - total_width);
-}
+  switch (type)
+    {
+    case FMT_F:
+    case FMT_COMMA:
+    case FMT_DOT:
+    case FMT_DOLLAR:
+    case FMT_PCT:
+    case FMT_E:
+      return (settings->decimal == '.'
+              ? &period_styles[type]
+              : &comma_styles[type]);
+
+    case FMT_CCA:
+    case FMT_CCB:
+    case FMT_CCC:
+    case FMT_CCD:
+    case FMT_CCE:
+      {
+        size_t idx = fmt_type_to_cc_index (type);
+        return settings->ccs[idx] ? settings->ccs[idx] : &default_style;
+      }
 
-/* Sets the decimal point character for the settings in S to DECIMAL.
+    default:
+      return &default_style;
+    }
+}
 
-   This has no effect on custom currency formats. */
 void
-fmt_settings_set_decimal (struct fmt_settings *s, char decimal)
+fmt_settings_set_cc (struct fmt_settings *settings, enum fmt_type type,
+                     struct fmt_number_style *style)
 {
-  int grouping = decimal == '.' ? ',' : '.';
-  assert (decimal == '.' || decimal == ',');
-
-  fmt_settings_set_style (s, FMT_F,      decimal,        0, "-",  "",  "", "");
-  fmt_settings_set_style (s, FMT_E,      decimal,        0, "-",  "",  "", "");
-  fmt_settings_set_style (s, FMT_COMMA,  decimal, grouping, "-",  "",  "", "");
-  fmt_settings_set_style (s, FMT_DOT,   grouping,  decimal, "-",  "",  "", "");
-  fmt_settings_set_style (s, FMT_DOLLAR, decimal, grouping, "-", "$",  "", "");
-  fmt_settings_set_style (s, FMT_PCT,    decimal,        0, "-",  "", "%", "");
+  size_t idx = fmt_type_to_cc_index (type);
+
+  fmt_number_style_destroy (settings->ccs[idx]);
+  settings->ccs[idx] = style;
 }
 
+\f
 /* Returns an input format specification with type TYPE, width W,
    and D decimals. */
 struct fmt_spec
@@ -1137,12 +1131,13 @@ fmt_clamp_decimals (struct fmt_spec *fmt, enum fmt_use use)
     fmt->d = max_d;
 }
 \f
-/* Sets AFFIX's string value to S, a UTF-8 encoded string. */
-static void
-fmt_affix_set (struct fmt_affix *affix, const char *s)
+static struct fmt_affix
+fmt_affix_clone (const struct fmt_affix *old)
 {
-  affix->s = s[0] == '\0' ? CONST_CAST (char *, "") : xstrdup (s);
-  affix->width = u8_strwidth (CHAR_CAST (const uint8_t *, s), "UTF-8");
+  return (struct fmt_affix) {
+    .s = old->s ? xstrdup (old->s) : NULL,
+    .width = old->width,
+  };
 }
 
 /* Frees data in AFFIX. */
@@ -1153,32 +1148,99 @@ fmt_affix_free (struct fmt_affix *affix)
     free (affix->s);
 }
 
-static void
-fmt_number_style_init (struct fmt_number_style *style)
+/* Find and returns the grouping character in CC_STRING (either '.' or ',') or
+   0 on error. */
+static int
+find_cc_separators (const char *cc_string)
+{
+  /* Count commas and periods.  There must be exactly three of
+     one or the other, except that an apostrophe escapes a
+     following comma or period. */
+  int n_commas = 0;
+  int n_dots = 0;
+  for (const char *p = cc_string; *p; p++)
+    if (*p == ',')
+      n_commas++;
+    else if (*p == '.')
+      n_dots++;
+    else if (*p == '\'' && (p[1] == '.' || p[1] == ',' || p[1] == '\''))
+      p++;
+
+  return (n_commas == 3 ? (n_dots != 3 ? ',' : 0)
+          : n_dots == 3 ? '.'
+          : 0);
+}
+
+/* Extracts a token from IN into a newly allocated string AFFIXP.  Tokens are
+   delimited by GROUPING.  Returns the first character following the token. */
+static struct fmt_affix
+extract_cc_token (const char **sp, int grouping, size_t *extra_bytes)
 {
-  fmt_affix_set (&style->neg_prefix, "");
-  fmt_affix_set (&style->prefix, "");
-  fmt_affix_set (&style->suffix, "");
-  fmt_affix_set (&style->neg_suffix, "");
-  style->decimal = '.';
-  style->grouping = 0;
+  const char *p = *sp;
+  for (; *p && *p != grouping; p++)
+    if (*p == '\'' && p[1] == grouping)
+      p++;
+
+  size_t length = p - *sp;
+  char *affix = xmemdup0 (*sp, length);
+  size_t width = u8_strwidth (CHAR_CAST (const uint8_t *, affix), "UTF-8");
+  if (length > width)
+    *extra_bytes += length - width;
+
+  *sp = p + (*p != 0);
+
+  return (struct fmt_affix) { .s = affix, .width = width };
 }
 
-static void
-fmt_number_style_clone (struct fmt_number_style *new,
-                        const struct fmt_number_style *old)
+struct fmt_number_style *
+fmt_number_style_from_string (const char *s)
 {
-  fmt_affix_set (&new->neg_prefix, old->neg_prefix.s);
-  fmt_affix_set (&new->prefix, old->prefix.s);
-  fmt_affix_set (&new->suffix, old->suffix.s);
-  fmt_affix_set (&new->neg_suffix, old->neg_suffix.s);
-  new->decimal = old->decimal;
-  new->grouping = old->grouping;
-  new->extra_bytes = old->extra_bytes;
+  char grouping = find_cc_separators (s);
+  if (!grouping)
+    return NULL;
+
+  size_t extra_bytes = 0;
+  struct fmt_affix neg_prefix = extract_cc_token (&s, grouping, &extra_bytes);
+  struct fmt_affix prefix = extract_cc_token (&s, grouping, &extra_bytes);
+  struct fmt_affix suffix = extract_cc_token (&s, grouping, &extra_bytes);
+  struct fmt_affix neg_suffix = extract_cc_token (&s, grouping, &extra_bytes);
+
+  struct fmt_number_style *style = xmalloc (sizeof *style);
+  *style = (struct fmt_number_style) {
+    .neg_prefix = neg_prefix,
+    .prefix = prefix,
+    .suffix = suffix,
+    .neg_suffix = neg_suffix,
+    .decimal = grouping == '.' ? ',' : '.',
+    .grouping = grouping,
+    .extra_bytes = extra_bytes,
+  };
+  return style;
+}
+
+struct fmt_number_style *
+fmt_number_style_clone (const struct fmt_number_style *old)
+{
+  if (old)
+    {
+      struct fmt_number_style *new = xmalloc (sizeof *new);
+      *new = (struct fmt_number_style) {
+        .neg_prefix = fmt_affix_clone (&old->neg_prefix),
+        .prefix = fmt_affix_clone (&old->prefix),
+        .suffix = fmt_affix_clone (&old->suffix),
+        .neg_suffix = fmt_affix_clone (&old->neg_suffix),
+        .decimal = old->decimal,
+        .grouping = old->grouping,
+        .extra_bytes = old->extra_bytes,
+      };
+      return new;
+    }
+  else
+    return NULL;
 }
 
 /* Destroys a struct fmt_number_style. */
-static void
+void
 fmt_number_style_destroy (struct fmt_number_style *style)
 {
   if (style != NULL)
@@ -1187,6 +1249,7 @@ fmt_number_style_destroy (struct fmt_number_style *style)
       fmt_affix_free (&style->prefix);
       fmt_affix_free (&style->suffix);
       fmt_affix_free (&style->neg_suffix);
+      free (style);
     }
 }
 
index 09c6331ef223ae94bd2a9ea3166c37fa75dc6801..3637b95d0cffe8716825f77778db61805658e4e0 100644 (file)
@@ -139,23 +139,6 @@ bool fmt_from_u32 (uint32_t, int var_width, bool loose, struct fmt_spec *);
 const char *fmt_date_template (enum fmt_type, int width) PURE_FUNCTION;
 const char *fmt_gui_name (enum fmt_type);
 \f
-/* Format settings.
-
-   A fmt_settings is really just a collection of one "struct fmt_number_style"
-   for each format type. */
-struct fmt_settings *fmt_settings_create (void);
-void fmt_settings_destroy (struct fmt_settings *);
-struct fmt_settings *fmt_settings_clone (const struct fmt_settings *);
-
-void fmt_settings_set_decimal (struct fmt_settings *, char);
-
-const struct fmt_number_style *fmt_settings_get_style (
-  const struct fmt_settings *, enum fmt_type);
-void fmt_settings_set_style (struct fmt_settings *, enum fmt_type,
-                             char decimal, char grouping,
-                             const char *neg_prefix, const char *prefix,
-                             const char *suffix, const char *neg_suffix);
-\f
 /* A prefix or suffix for a numeric output format. */
 struct fmt_affix
   {
@@ -163,7 +146,8 @@ struct fmt_affix
     int width;                  /* Display width in columns (see wcwidth()). */
   };
 
-/* A numeric output style. */
+/* A numeric output style.  This can express the basic numeric formats (in the
+   FMT_CAT_BASIC category) and custom currency formats (FMT_CCx). */
 struct fmt_number_style
   {
     struct fmt_affix neg_prefix; /* Negative prefix. */
@@ -183,9 +167,34 @@ struct fmt_number_style
     int extra_bytes;
   };
 
+struct fmt_number_style *fmt_number_style_from_string (const char *);
+struct fmt_number_style *fmt_number_style_clone (
+  const struct fmt_number_style *);
+void fmt_number_style_destroy (struct fmt_number_style *);
+
 int fmt_affix_width (const struct fmt_number_style *);
 int fmt_neg_affix_width (const struct fmt_number_style *);
+\f
+/* Number of custom currency styles. */
+#define FMT_N_CCS 5             /* FMT_CCA through FMT_CCE. */
 
+struct fmt_settings
+  {
+    char decimal;                            /* '.' or ','. */
+    struct fmt_number_style *ccs[FMT_N_CCS]; /* CCA through CCE. */
+  };
+#define FMT_SETTINGS_INIT { .decimal = '.' }
+
+void fmt_settings_init (struct fmt_settings *);
+void fmt_settings_uninit (struct fmt_settings *);
+void fmt_settings_copy (struct fmt_settings *, const struct fmt_settings *);
+
+const struct fmt_number_style *fmt_settings_get_style (
+  const struct fmt_settings *, enum fmt_type);
+
+void fmt_settings_set_cc (struct fmt_settings *, enum fmt_type,
+                          struct fmt_number_style *);
+\f
 extern const struct fmt_spec F_8_0 ;
 extern const struct fmt_spec F_8_2 ;
 extern const struct fmt_spec F_4_3 ;
index db9c3a16329d88d16f07da86ac7a88816f1208e9..37b609dcfb0031aaeb48f9f457c7a13d4d51c3ed 100644 (file)
@@ -72,7 +72,7 @@ struct settings
   int global_algorithm;
   int syntax;
 
-  struct fmt_settings *styles;
+  struct fmt_settings styles;
 
   enum settings_output_devices output_routing[SETTINGS_N_OUTPUT_TYPES];
 
@@ -113,7 +113,7 @@ static struct settings the_settings = {
   ENHANCED,                     /* cmd_algorithm */
   ENHANCED,                     /* global_algorithm */
   ENHANCED,                     /* syntax */
-  NULL,                         /* styles */
+  FMT_SETTINGS_INIT,            /* styles */
 
   /* output_routing */
   {SETTINGS_DEVICE_LISTING | SETTINGS_DEVICE_TERMINAL,
@@ -130,8 +130,6 @@ void
 settings_init (void)
 {
   settings_set_epoch (-1);
-  the_settings.styles = fmt_settings_create ();
-
   settings_set_decimal_char (get_system_decimal ());
 }
 
@@ -146,7 +144,7 @@ static void
 settings_copy (struct settings *dst, const struct settings *src)
 {
   *dst = *src;
-  dst->styles = fmt_settings_clone (src->styles);
+  fmt_settings_copy (&dst->styles, &src->styles);
 }
 
 /* Returns a copy of the current settings. */
@@ -173,7 +171,7 @@ settings_destroy (struct settings *s)
 {
   if (s != NULL)
     {
-      fmt_settings_destroy (s->styles);
+      fmt_settings_uninit (&s->styles);
       if (s != &the_settings)
         free (s);
     }
@@ -544,76 +542,13 @@ settings_set_syntax (enum behavior_mode mode)
 }
 
 \f
-
-/* Find the grouping characters in CC_STRING and sets *GROUPING and *DECIMAL
-   appropriately.  Returns true if successful, false otherwise. */
-static bool
-find_cc_separators (const char *cc_string, char *decimal, char *grouping)
-{
-  const char *sp;
-  int comma_cnt, dot_cnt;
-
-  /* Count commas and periods.  There must be exactly three of
-     one or the other, except that an apostrophe escapes a
-     following comma or period. */
-  comma_cnt = dot_cnt = 0;
-  for (sp = cc_string; *sp; sp++)
-    if (*sp == ',')
-      comma_cnt++;
-    else if (*sp == '.')
-      dot_cnt++;
-    else if (*sp == '\'' && (sp[1] == '.' || sp[1] == ',' || sp[1] == '\''))
-      sp++;
-
-  if ((comma_cnt == 3) == (dot_cnt == 3))
-    return false;
-
-  if (comma_cnt == 3)
-    {
-      *decimal = '.';
-      *grouping = ',';
-    }
-  else
-    {
-      *decimal = ',';
-      *grouping = '.';
-    }
-  return true;
-}
-
-/* Extracts a token from IN into a newly allocated string AFFIXP.  Tokens are
-   delimited by GROUPING.  Returns the first character following the token. */
-static const char *
-extract_cc_token (const char *in, int grouping, char **affixp)
-{
-  char *out;
-
-  out = *affixp = xmalloc (strlen (in) + 1);
-  for (; *in != '\0' && *in != grouping; in++)
-    {
-      if (*in == '\'' && in[1] == grouping)
-        in++;
-      *out++ = *in;
-    }
-  *out = '\0';
-
-  if (*in == grouping)
-    in++;
-  return in;
-}
-
 /* Sets custom currency specifier CC having name CC_NAME ('A' through
    'E') to correspond to the settings in CC_STRING. */
 bool
 settings_set_cc (const char *cc_string, enum fmt_type type)
 {
-  char *neg_prefix, *prefix, *suffix, *neg_suffix;
-  char decimal, grouping;
-
-  assert (fmt_get_category (type) == FMT_CAT_CUSTOM);
-
-  /* Determine separators. */
-  if (!find_cc_separators (cc_string, &decimal, &grouping))
+  struct fmt_number_style *style = fmt_number_style_from_string (cc_string);
+  if (!style)
     {
       msg (SE, _("%s: Custom currency string `%s' does not contain "
                  "exactly three periods or commas (or it contains both)."),
@@ -621,19 +556,7 @@ settings_set_cc (const char *cc_string, enum fmt_type type)
       return false;
     }
 
-  cc_string = extract_cc_token (cc_string, grouping, &neg_prefix);
-  cc_string = extract_cc_token (cc_string, grouping, &prefix);
-  cc_string = extract_cc_token (cc_string, grouping, &suffix);
-  cc_string = extract_cc_token (cc_string, grouping, &neg_suffix);
-
-  fmt_settings_set_style (the_settings.styles, type, decimal, grouping,
-                          neg_prefix, prefix, suffix, neg_suffix);
-
-  free (neg_suffix);
-  free (suffix);
-  free (prefix);
-  free (neg_prefix);
-
+  fmt_settings_set_cc (&the_settings.styles, type, style);
   return true;
 }
 
@@ -641,13 +564,13 @@ settings_set_cc (const char *cc_string, enum fmt_type type)
 int
 settings_get_decimal_char (enum fmt_type type)
 {
-  return fmt_settings_get_style (the_settings.styles, type)->decimal;
+  return fmt_settings_get_style (&the_settings.styles, type)->decimal;
 }
 
 void
 settings_set_decimal_char (char decimal)
 {
-  fmt_settings_set_decimal (the_settings.styles, decimal);
+  the_settings.styles.decimal = decimal;
 }
 
 /* Returns the number formatting style associated with the given
@@ -656,7 +579,7 @@ const struct fmt_number_style *
 settings_get_style (enum fmt_type type)
 {
   assert (is_fmt_type (type));
-  return fmt_settings_get_style (the_settings.styles, type);
+  return fmt_settings_get_style (&the_settings.styles, type);
 }
 
 /* Returns a string of the form "$#,###.##" according to FMT,
@@ -671,7 +594,7 @@ settings_dollar_template (const struct fmt_spec *fmt)
 
   assert (fmt->type == FMT_DOLLAR);
 
-  fns = fmt_settings_get_style (the_settings.styles, fmt->type);
+  fns = fmt_settings_get_style (&the_settings.styles, fmt->type);
 
   ds_put_byte (&str, '$');
   for (c = MAX (fmt->w - fmt->d - 1, 0); c > 0;)