work on monolithic rendering
[pspp] / src / data / format.c
index 79c6240a9125423ec986148a82d067701c4e048c..73e5dd60f4259df5932f60002a37a0c86c7225b0 100644 (file)
@@ -101,26 +101,38 @@ fmt_settings_get_style (const struct fmt_settings *settings,
 
 #define OPPOSITE(C) ((C) == ',' ? '.' : ',')
 #define AFFIX(S) { .s = (char *) (S), .width = sizeof (S) - 1 }
-#define NS(PREFIX, SUFFIX, DECIMAL, GROUPING) { \
+#define NS(PREFIX, SUFFIX, DECIMAL, GROUPING, INCLUDE_LEADING_ZERO) {        \
     .neg_prefix = AFFIX ("-"),                  \
     .prefix = AFFIX (PREFIX),                   \
     .suffix = AFFIX (SUFFIX),                   \
     .neg_suffix = AFFIX (""),                   \
     .decimal = DECIMAL,                         \
     .grouping = GROUPING,                       \
+    .include_leading_zero = INCLUDE_LEADING_ZERO \
   }
-#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),            \
+#define ANS(DECIMAL, GROUPING, INCLUDE_LEADING_ZERO) {                  \
+    [FMT_F]      = NS( "",  "", DECIMAL, 0, INCLUDE_LEADING_ZERO),      \
+    [FMT_E]      = NS( "",  "", DECIMAL, 0, INCLUDE_LEADING_ZERO),      \
+    [FMT_COMMA]  = NS( "",  "", DECIMAL, GROUPING, INCLUDE_LEADING_ZERO), \
+    [FMT_DOT]    = NS( "",  "", GROUPING, DECIMAL, INCLUDE_LEADING_ZERO), \
+    [FMT_DOLLAR] = NS("$",  "", DECIMAL, GROUPING, false),              \
+    [FMT_PCT]    = NS( "", "%", DECIMAL, 0, false),                     \
   }
+#define ANS2(DECIMAL, GROUPING) {               \
+    ANS(DECIMAL, GROUPING, false),              \
+    ANS(DECIMAL, GROUPING, true),               \
+  }
+
+  /* First index: 0 for ',' decimal point, 1 for '.' decimal point.
+     Second index: 0 for no leading zero, 1 for leading zero.
+     Third index: TYPE.
+  */
+  static const struct fmt_number_style styles[2][2][6] = {
+    ANS2 (',', '.'),
+    ANS2 ('.', ','),
+  };
 
-  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);
+  static const struct fmt_number_style default_style = NS ("", "", '.', 0, false);
 
   switch (type)
     {
@@ -130,9 +142,11 @@ fmt_settings_get_style (const struct fmt_settings *settings,
     case FMT_DOLLAR:
     case FMT_PCT:
     case FMT_E:
-      return (settings->decimal == '.'
-              ? &period_styles[type]
-              : &comma_styles[type]);
+      {
+        int decimal_idx = settings->decimal == '.';
+        int leadzero_idx = settings->include_leading_zero;
+        return &styles[decimal_idx][leadzero_idx][type];
+      }
 
     case FMT_CCA:
     case FMT_CCB:
@@ -396,53 +410,56 @@ fmt_check__ (const struct fmt_spec *spec, enum fmt_use use)
         return (use == FMT_FOR_INPUT
                 ? xasprintf (ngettext (
                                "Input format %s specifies %d decimal place, "
-                               "but the given width allows at most "
-                               "%d decimals.",
+                               "but width %d allows at most %d decimals.",
                                "Input format %s specifies %d decimal places, "
-                               "but the given width allows at most "
-                               "%d decimals.",
+                               "but width %d allows at most %d decimals.",
                                spec->d),
-                             str, spec->d, max_d)
+                             str, spec->d, spec->w, max_d)
                 : xasprintf (ngettext (
                                "Output format %s specifies %d decimal place, "
-                               "but the given width allows at most "
-                               "%d decimals.",
+                               "but width %d allows at most %d decimals.",
                                "Output format %s specifies %d decimal places, "
-                               "but the given width allows at most "
-                               "%d decimals.",
+                               "but width %d allows at most %d decimals.",
                                spec->d),
-                             str, spec->d, max_d));
+                             str, spec->d, spec->w, max_d));
       else
         return (use == FMT_FOR_INPUT
                 ? xasprintf (ngettext (
                                "Input format %s specifies %d decimal place, "
-                               "but the given width does not allow "
-                               "for any decimals.",
+                               "but width %d does not allow for any decimals.",
                                "Input format %s specifies %d decimal places, "
-                               "but the given width does not allow "
-                               "for any decimals.",
+                               "but width %d does not allow for any decimals.",
                                spec->d),
-                             str, spec->d)
+                             str, spec->d, spec->w)
                 : xasprintf (ngettext (
                                "Output format %s specifies %d decimal place, "
-                               "but the given width does not allow "
-                               "for any decimals.",
+                               "but width %d does not allow for any decimals.",
                                "Output format %s specifies %d decimal places, "
-                               "but the given width does not allow "
-                               "for any decimals.",
+                               "but width %d does not allow for any decimals.",
                                spec->d),
-                             str, spec->d));
+                             str, spec->d, spec->w));
     }
 
   return NULL;
 }
 
+char *
+fmt_check_input__ (const struct fmt_spec *spec)
+{
+  return fmt_check__ (spec, FMT_FOR_INPUT);
+}
+
+char *
+fmt_check_output__ (const struct fmt_spec *spec)
+{
+  return fmt_check__ (spec, FMT_FOR_OUTPUT);
+}
+
 static bool
-fmt_emit_and_free_error (char *error)
+error_to_bool (char *error)
 {
   if (error)
     {
-      msg (SE, "%s", error);
       free (error);
       return false;
     }
@@ -450,25 +467,21 @@ fmt_emit_and_free_error (char *error)
     return true;
 }
 
-/* Checks whether SPEC is valid for USE and returns nonzero if so.  Otherwise,
-   emits an error message for the current source location and returns zero. */
+/* Returns true if SPEC is valid for USE, false otherwise. */
 bool
 fmt_check (const struct fmt_spec *spec, enum fmt_use use)
 {
-  return fmt_emit_and_free_error (fmt_check__ (spec, use));
+  return error_to_bool (fmt_check__ (spec, use));
 }
 
-/* Checks whether SPEC is valid as an input format and returns
-   nonzero if so.  Otherwise, emits an error message and returns
-   zero. */
+/* Returns true if SPEC is valid as an input format, otherwise false. */
 bool
 fmt_check_input (const struct fmt_spec *spec)
 {
   return fmt_check (spec, FMT_FOR_INPUT);
 }
 
-/* Checks whether SPEC is valid as an output format and returns
-   true if so.  Otherwise, emits an error message and returns false. */
+/* Returnst true SPEC is valid as an output format, false otherwise. */
 bool
 fmt_check_output (const struct fmt_spec *spec)
 {
@@ -477,59 +490,91 @@ fmt_check_output (const struct fmt_spec *spec)
 
 /* Checks that FORMAT is appropriate for a variable of the given VAR_TYPE and
    returns NULL if so.  Otherwise returns a malloc()'d error message that the
-   calelr must eventually free(). */
+   caller must eventually free().  VARNAME is optional and only used in the
+   error message.*/
 char *
-fmt_check_type_compat__ (const struct fmt_spec *format, enum val_type var_type)
+fmt_check_type_compat__ (const struct fmt_spec *format, const char *varname,
+                         enum val_type var_type)
 {
   assert (val_type_is_valid (var_type));
   if ((var_type == VAL_STRING) != (fmt_is_string (format->type) != 0))
     {
       char str[FMT_STRING_LEN_MAX + 1];
-      return xasprintf (_("%s variables are not compatible with %s format %s."),
-                        var_type == VAL_STRING ? _("String") : _("Numeric"),
-                        var_type == VAL_STRING ? _("numeric") : _("string"),
-                        fmt_to_string (format, str));
+      fmt_to_string (format, str);
+      if (var_type == VAL_STRING)
+        {
+          if (varname)
+            return xasprintf (_("String variable %s is not compatible with "
+                                "numeric format %s."), varname, str);
+          else
+            return xasprintf (_("String variables are not compatible with "
+                                "numeric format %s."), str);
+        }
+      else
+        {
+          if (varname)
+            return xasprintf (_("Numeric variable %s is not compatible with "
+                                "string format %s."), varname, str);
+          else
+            return xasprintf (_("Numeric variables are not compatible with "
+                                "string format %s."), str);
+        }
     }
   return NULL;
 }
 
-/* Checks that FORMAT is appropriate for a variable of the given
-   VAR_TYPE and returns true if so.  Otherwise returns false and
-   emits an error message. */
+/* Returns FORMAT is appropriate for a variable of the given VAR_TYPE and
+   returns true if so, otherwise false. */
 bool
 fmt_check_type_compat (const struct fmt_spec *format, enum val_type var_type)
 {
-  return fmt_emit_and_free_error (fmt_check_type_compat__ (format, var_type));
+  return error_to_bool (fmt_check_type_compat__ (format, NULL, var_type));
 }
 
 /* Checks that FORMAT is appropriate for a variable of the given WIDTH and
    returns NULL if so.  Otherwise returns a malloc()'d error message that the
-   calelr must eventually free(). */
+   caller must eventually free().  VARNAME is optional and only used in the
+   error message. */
 char *
-fmt_check_width_compat__ (const struct fmt_spec *format, int width)
+fmt_check_width_compat__ (const struct fmt_spec *format, const char *varname,
+                          int width)
 {
-  char *error = fmt_check_type_compat__ (format, val_type_from_width (width));
+  char *error = fmt_check_type_compat__ (format, varname,
+                                         val_type_from_width (width));
   if (error)
     return error;
 
   if (fmt_var_width (format) != width)
     {
-      char str[FMT_STRING_LEN_MAX + 1];
-      return xasprintf (_("String variable with width %d is not compatible "
-                          "with format %s."),
-                        width, fmt_to_string (format, str));
+      char format_str[FMT_STRING_LEN_MAX + 1];
+      fmt_to_string (format, format_str);
+
+      char better_str[FMT_STRING_LEN_MAX + 1];
+      if (format->type == FMT_A)
+        snprintf (better_str, sizeof better_str, "A%d", width);
+      else
+        snprintf (better_str, sizeof better_str, "AHEX%d", width * 2);
+
+      if (varname)
+        return xasprintf (_("String variable %s with width %d is not "
+                            "compatible with format %s.  "
+                            "Use format %s instead."),
+                          varname, width, format_str, better_str);
+      else
+        return xasprintf (_("String variable with width %d is not compatible "
+                            "with format %s.  Use format %s instead."),
+                          width, format_str, better_str);
     }
 
   return NULL;
 }
 
-/* Checks that FORMAT is appropriate for a variable of the given
-   WIDTH and returns true if so.  Otherwise returns false and
-   emits an error message. */
+/* Checks that FORMAT is appropriate for a variable of the given WIDTH and
+   returns true if so, otherwise false. */
 bool
 fmt_check_width_compat (const struct fmt_spec *format, int width)
 {
-  return fmt_emit_and_free_error (fmt_check_width_compat__ (format, width));
+  return error_to_bool (fmt_check_width_compat__ (format, NULL, width));
 }
 
 /* Returns the width corresponding to FORMAT.  The return value
@@ -994,22 +1039,18 @@ fmt_from_u32 (uint32_t u32, int width, bool loose, struct fmt_spec *f)
   uint8_t w = u32 >> 8;
   uint8_t d = u32;
 
-  msg_disable ();
-  f->w = w;
-  f->d = d;
-  bool ok = fmt_from_io (raw_type, &f->type);
-  if (ok)
-    {
-      if (loose)
-        fmt_fix_output (f);
-      else
-        ok = fmt_check_output (f);
-    }
-  if (ok)
-    ok = fmt_check_width_compat (f, width);
-  msg_enable ();
+  enum fmt_type type;
+  if (!fmt_from_io (raw_type, &type))
+    return false;
+
+  *f = (struct fmt_spec) { .type = type, .w = w, .d = d };
+
+  if (loose)
+    fmt_fix_output (f);
+  else if (!fmt_check_output (f))
+    return false;
 
-  return ok;
+  return fmt_check_width_compat (f, width);
 }
 
 /* Returns true if TYPE may be used as an input format,
@@ -1298,6 +1339,7 @@ fmt_number_style_from_string (const char *s)
     .neg_suffix = neg_suffix,
     .decimal = grouping == '.' ? ',' : '.',
     .grouping = grouping,
+    .include_leading_zero = false,
     .extra_bytes = extra_bytes,
   };
   return style;