-/* PSPP - computes sample statistics.
- Copyright (C) 1997-9, 2000, 2006 Free Software Foundation, Inc.
- Written by Ben Pfaff <blp@gnu.org>.
+/* PSPP - a program for statistical analysis.
+ Copyright (C) 1997-9, 2000, 2006, 2010, 2011, 2012 Free Software Foundation, Inc.
- This program 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 2 of the
- License, or (at your option) any later version.
+ This program 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.
- This program 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.
+ This program 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 this program; if not, write to the Free Software
- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- 02110-1301, USA. */
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
#include <config.h>
#include <ctype.h>
#include <stdlib.h>
-
-#include <data/identifier.h>
-#include <data/settings.h>
-#include <data/variable.h>
-#include <libpspp/assertion.h>
-#include <libpspp/compiler.h>
-#include <libpspp/message.h>
-#include <libpspp/misc.h>
-#include <libpspp/str.h>
-
-#include "minmax.h"
-#include "xalloc.h"
+#include <time.h>
+#include <uniwidth.h>
+
+#include "data/identifier.h"
+#include "data/settings.h"
+#include "data/value.h"
+#include "data/variable.h"
+#include "libpspp/assertion.h"
+#include "libpspp/cast.h"
+#include "libpspp/compiler.h"
+#include "libpspp/message.h"
+#include "libpspp/misc.h"
+#include "libpspp/str.h"
+
+#include "gl/c-strcase.h"
+#include "gl/minmax.h"
+#include "gl/xalloc.h"
+#include "gl/xmemdup0.h"
#include "gettext.h"
#define _(msgid) gettext (msgid)
-static bool is_fmt_type (enum fmt_type);
+bool is_fmt_type (enum fmt_type);
-static int min_width (enum fmt_type, bool for_input);
-static int max_width (enum fmt_type);
-static bool valid_width (enum fmt_type, int width, bool for_input);
-static int max_decimals (enum fmt_type, int width, bool for_input);
+static bool valid_width (enum fmt_type, int width, enum fmt_use);
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);
+
+void
+fmt_settings_init (struct fmt_settings *settings)
+{
+ *settings = (struct fmt_settings) FMT_SETTINGS_INIT;
+}
-/* Initialize the format module. */
void
-fmt_init (void)
+fmt_settings_uninit (struct fmt_settings *settings)
+{
+ for (int i = 0; i < FMT_N_CCS; i++)
+ fmt_number_style_destroy (settings->ccs[i]);
+}
+
+struct fmt_settings
+fmt_settings_copy (const struct fmt_settings *old)
+{
+ struct fmt_settings new = *old;
+ 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
+ format TYPE. */
+const struct fmt_number_style *
+fmt_settings_get_style (const struct fmt_settings *settings,
+ enum fmt_type 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, 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, 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 default_style = NS ("", "", '.', 0, false);
+
+ switch (type)
+ {
+ case FMT_F:
+ case FMT_COMMA:
+ case FMT_DOT:
+ case FMT_DOLLAR:
+ case FMT_PCT:
+ case FMT_E:
+ {
+ 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:
+ 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;
+ }
+
+ default:
+ return &default_style;
+ }
+}
+
+static int
+default_epoch (void)
{
- static bool inited = false;
- if (!inited)
+ static int epoch = 0;
+ if (!epoch)
{
- inited = true;
- fmt_set_decimal ('.');
+ time_t t = time (0);
+ struct tm *tm = localtime (&t);
+ epoch = (tm != NULL ? tm->tm_year + 1900 : 2000) - 69;
}
+ return epoch;
+}
+
+int
+fmt_settings_get_epoch (const struct fmt_settings *settings)
+{
+ return !settings->epoch ? default_epoch () : settings->epoch;
}
-/* Deinitialize the format module. */
void
-fmt_done (void)
+fmt_settings_set_cc (struct fmt_settings *settings, enum fmt_type type,
+ struct fmt_number_style *style)
{
+ 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
fmt_for_input (enum fmt_type type, int w, int d)
{
- struct fmt_spec f;
- f.type = type;
- f.w = w;
- f.d = d;
+ struct fmt_spec f = { .type = type, .w = w, .d = d };
assert (fmt_check_input (&f));
return f;
}
struct fmt_spec
fmt_for_output (enum fmt_type type, int w, int d)
{
- struct fmt_spec f;
- f.type = type;
- f.w = w;
- f.d = d;
+ struct fmt_spec f = { .type = type, .w = w, .d = d };
assert (fmt_check_output (&f));
return f;
}
/* Returns the output format specifier corresponding to input
format specifier INPUT. */
struct fmt_spec
-fmt_for_output_from_input (const struct fmt_spec *input)
+fmt_for_output_from_input (const struct fmt_spec *input,
+ const struct fmt_settings *settings)
{
struct fmt_spec output;
case FMT_DOLLAR:
case FMT_PCT:
{
- const struct fmt_number_style *style = fmt_get_style (input->type);
+ const struct fmt_number_style *style =
+ fmt_settings_get_style (settings, input->type);
+
output.w += fmt_affix_width (style);
if (style->grouping != 0 && input->w - input->d >= 3)
output.w += (input->w - input->d - 1) / 3;
case FMT_MONTH:
break;
+ case FMT_MTIME:
+ if (input->d)
+ output.w = MAX (input->w, input->d + 6);
+ break;
+
+ case FMT_YMDHMS:
+ if (input->w)
+ output.w = MAX (input->w, input->d + 20);
+ break;
+
default:
NOT_REACHED ();
}
return output;
}
-/* Checks whether SPEC is valid as an input format (if FOR_INPUT)
- or an output format (otherwise) and returns nonzero if so.
- Otherwise, emits an error message and returns zero. */
-bool
-fmt_check (const struct fmt_spec *spec, bool for_input)
+/* Returns the default format for the given WIDTH: F8.2 format
+ for a numeric value, A format for a string value. */
+struct fmt_spec
+fmt_default_for_width (int width)
+{
+ return (width == 0
+ ? fmt_for_output (FMT_F, 8, 2)
+ : fmt_for_output (FMT_A, width, 0));
+}
+
+/* Checks whether SPEC is valid for USE and returns NULL if so. Otherwise,
+ returns a malloc()'d string that describes the error. The caller must
+ eventually free() the string. */
+char *
+fmt_check__ (const struct fmt_spec *spec, enum fmt_use use)
{
- const char *io_fmt = for_input ? _("Input format") : _("Output format");
char str[FMT_STRING_LEN_MAX + 1];
int min_w, max_w, max_d;
assert (is_fmt_type (spec->type));
fmt_to_string (spec, str);
- if (for_input && !fmt_usable_for_input (spec->type))
- {
- msg (SE, _("Format %s may not be used for input."), str);
- return false;
- }
+ if (use == FMT_FOR_INPUT && !fmt_usable_for_input (spec->type))
+ return xasprintf (_("Format %s may not be used for input."), str);
if (spec->w % fmt_step_width (spec->type))
{
assert (fmt_step_width (spec->type) == 2);
- msg (SE, _("%s specifies width %d, but %s requires an even width."),
- str, spec->w, fmt_name (spec->type));
- return false;
+ return (use == FMT_FOR_INPUT
+ ? xasprintf (_("Input format %s specifies width %d, "
+ "but %s requires an even width."),
+ str, spec->w, fmt_name (spec->type))
+ : xasprintf (_("Output format %s specifies width %d, "
+ "but %s requires an even width."),
+ str, spec->w, fmt_name (spec->type)));
}
- min_w = min_width (spec->type, for_input);
- max_w = max_width (spec->type);
+ min_w = fmt_min_width (spec->type, use);
+ max_w = fmt_max_width (spec->type, use);
if (spec->w < min_w || spec->w > max_w)
- {
- msg (SE, _("%s %s specifies width %d, but "
- "%s requires a width between %d and %d."),
- io_fmt, str, spec->w, fmt_name (spec->type), min_w, max_w);
- return false;
- }
-
- max_d = max_decimals (spec->type, spec->w, for_input);
+ return (use == FMT_FOR_INPUT
+ ? xasprintf (_("Input format %s specifies width %d, but "
+ "%s requires a width between %d and %d."),
+ str, spec->w, fmt_name (spec->type), min_w, max_w)
+ : xasprintf (_("Output format %s specifies width %d, but "
+ "%s requires a width between %d and %d."),
+ str, spec->w, fmt_name (spec->type), min_w, max_w));
+
+ max_d = fmt_max_decimals (spec->type, spec->w, use);
if (!fmt_takes_decimals (spec->type) && spec->d != 0)
- {
- msg (SE, ngettext ("%s %s specifies %d decimal place, but "
- "%s does not allow any decimals.",
- "%s %s specifies %d decimal places, but "
- "%s does not allow any decimals.",
- spec->d),
- io_fmt, str, spec->d, fmt_name (spec->type));
- return false;
- }
+ return (use == FMT_FOR_INPUT
+ ? xasprintf (ngettext (
+ "Input format %s specifies %d decimal "
+ "place, but %s does not allow any decimals.",
+ "Input format %s specifies %d decimal "
+ "places, but %s does not allow any "
+ "decimals.",
+ spec->d),
+ str, spec->d, fmt_name (spec->type))
+ : xasprintf (ngettext (
+ "Output format %s specifies %d decimal "
+ "place, but %s does not allow any decimals.",
+ "Output format %s specifies %d decimal places, but "
+ "%s does not allow any decimals.",
+ spec->d),
+ str, spec->d, fmt_name (spec->type)));
else if (spec->d > max_d)
{
if (max_d > 0)
- msg (SE, ngettext ("%s %s specifies %d decimal place, but "
- "the given width allows at most %d decimals.",
- "%s %s specifies %d decimal places, but "
- "the given width allows at most %d decimals.",
- spec->d),
- io_fmt, str, spec->d, max_d);
+ return (use == FMT_FOR_INPUT
+ ? xasprintf (ngettext (
+ "Input format %s specifies %d decimal place, "
+ "but the given width allows at most "
+ "%d decimals.",
+ "Input format %s specifies %d decimal places, "
+ "but the given width allows at most "
+ "%d decimals.",
+ spec->d),
+ str, spec->d, max_d)
+ : xasprintf (ngettext (
+ "Output format %s specifies %d decimal place, "
+ "but the given width allows at most "
+ "%d decimals.",
+ "Output format %s specifies %d decimal places, "
+ "but the given width allows at most "
+ "%d decimals.",
+ spec->d),
+ str, spec->d, max_d));
else
- msg (SE, ngettext ("%s %s specifies %d decimal place, but "
- "the given width does not allow for any decimals.",
- "%s %s specifies %d decimal places, but "
- "the given width does not allow for any decimals.",
- spec->d),
- io_fmt, str, spec->d);
+ return (use == FMT_FOR_INPUT
+ ? xasprintf (ngettext (
+ "Input format %s specifies %d decimal place, "
+ "but the given width does not allow "
+ "for any decimals.",
+ "Input format %s specifies %d decimal places, "
+ "but the given width does not allow "
+ "for any decimals.",
+ spec->d),
+ str, spec->d)
+ : xasprintf (ngettext (
+ "Output format %s specifies %d decimal place, "
+ "but the given width does not allow "
+ "for any decimals.",
+ "Output format %s specifies %d decimal places, "
+ "but the given width does not allow "
+ "for any decimals.",
+ spec->d),
+ str, spec->d));
+ }
+
+ return NULL;
+}
+
+static bool
+fmt_emit_and_free_error (char *error)
+{
+ if (error)
+ {
+ msg (SE, "%s", error);
+ free (error);
return false;
}
+ else
+ return true;
+}
- 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. */
+bool
+fmt_check (const struct fmt_spec *spec, enum fmt_use use)
+{
+ return fmt_emit_and_free_error (fmt_check__ (spec, use));
}
/* Checks whether SPEC is valid as an input format and returns
bool
fmt_check_input (const struct fmt_spec *spec)
{
- return fmt_check (spec, true);
+ return fmt_check (spec, FMT_FOR_INPUT);
}
/* Checks whether SPEC is valid as an output format and returns
bool
fmt_check_output (const struct fmt_spec *spec)
{
- return fmt_check (spec, false);
+ return fmt_check (spec, FMT_FOR_OUTPUT);
+}
+
+/* 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(). */
+char *
+fmt_check_type_compat__ (const struct fmt_spec *format, 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));
+ }
+ return NULL;
}
/* Checks that FORMAT is appropriate for a variable of the given
- TYPE and returns true if so. Otherwise returns false and
+ VAR_TYPE and returns true if so. Otherwise returns false and
emits an error message. */
bool
-fmt_check_type_compat (const struct fmt_spec *format, int var_type)
+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));
+}
+
+/* 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(). */
+char *
+fmt_check_width_compat__ (const struct fmt_spec *format, int width)
{
- assert (var_type == NUMERIC || var_type == ALPHA);
- if ((var_type == ALPHA) != (fmt_is_string (format->type) != 0))
+ char *error = fmt_check_type_compat__ (format, val_type_from_width (width));
+ if (error)
+ return error;
+
+ if (fmt_var_width (format) != width)
{
char str[FMT_STRING_LEN_MAX + 1];
- msg (SE, _("%s variables are not compatible with %s format %s."),
- var_type == ALPHA ? _("String") : _("Numeric"),
- var_type == ALPHA ? _("numeric") : _("string"),
- fmt_to_string (format, str));
- return false;
+ return xasprintf (_("String variable with width %d is not compatible "
+ "with format %s."),
+ width, fmt_to_string (format, str));
}
- return true;
+
+ return NULL;
}
/* Checks that FORMAT is appropriate for a variable of the given
bool
fmt_check_width_compat (const struct fmt_spec *format, int width)
{
- if (!fmt_check_type_compat (format, width != 0 ? ALPHA : NUMERIC))
- return false;
- if (fmt_var_width (format) != width)
- {
- char str[FMT_STRING_LEN_MAX + 1];
- msg (SE, _("String variable with width %d is not compatible with "
- "format %s."),
- width, fmt_to_string (format, str));
- return false;
- }
- return true;
+ return fmt_emit_and_free_error (fmt_check_width_compat__ (format, width));
}
-/* Returns the width corresponding to the format specifier. The
- return value is the value of the `width' member of a `struct
- variable' for such an input format. */
+/* Returns the width corresponding to FORMAT. The return value
+ is the width of the `union value's required by FORMAT. */
int
-fmt_var_width (const struct fmt_spec *spec)
+fmt_var_width (const struct fmt_spec *format)
{
- return (spec->type == FMT_AHEX ? spec->w / 2
- : spec->type == FMT_A ? spec->w
+ return (format->type == FMT_AHEX ? format->w / 2
+ : format->type == FMT_A ? format->w
: 0);
}
"%s%d", fmt_name (f->type), f->w);
return buffer;
}
+
+/* Returns true if A and B are identical formats,
+ false otherwise. */
+bool
+fmt_equal (const struct fmt_spec *a, const struct fmt_spec *b)
+{
+ return a->type == b->type && a->w == b->w && a->d == b->d;
+}
+
+/* Adjusts FMT to be valid for a value of the given WIDTH if necessary.
+ If nothing needed to be changed the return value is false
+ */
+bool
+fmt_resize (struct fmt_spec *fmt, int width)
+{
+ if ((width > 0) != fmt_is_string (fmt->type))
+ {
+ /* Changed from numeric to string or vice versa. Set to
+ default format for new width. */
+ *fmt = fmt_default_for_width (width);
+ }
+ else if (width > 0)
+ {
+ /* Changed width of string. Preserve format type, adjust
+ width. */
+ fmt->w = fmt->type == FMT_AHEX ? width * 2 : width;
+ }
+ else
+ {
+ /* Still numeric. */
+ return false;
+ }
+ return true;
+}
+
+/* Adjusts FMT's width and decimal places to be valid for USE. */
+void
+fmt_fix (struct fmt_spec *fmt, enum fmt_use use)
+{
+ /* Clamp width to those allowed by format. */
+ fmt_clamp_width (fmt, use);
+
+ /* If FMT has more decimal places than allowed, attempt to increase FMT's
+ width until that number of decimal places can be achieved. */
+ if (fmt->d > fmt_max_decimals (fmt->type, fmt->w, use)
+ && fmt_takes_decimals (fmt->type))
+ {
+ int max_w = fmt_max_width (fmt->type, use);
+ for (; fmt->w < max_w; fmt->w++)
+ if (fmt->d <= fmt_max_decimals (fmt->type, fmt->w, use))
+ break;
+ }
+
+ /* Clamp decimals to those allowed by format and width. */
+ fmt_clamp_decimals (fmt, use);
+}
+
+/* Adjusts FMT's width and decimal places to be valid for an
+ input format. */
+void
+fmt_fix_input (struct fmt_spec *fmt)
+{
+ fmt_fix (fmt, FMT_FOR_INPUT);
+}
+
+/* Adjusts FMT's width and decimal places to be valid for an
+ output format. */
+void
+fmt_fix_output (struct fmt_spec *fmt)
+{
+ fmt_fix (fmt, FMT_FOR_OUTPUT);
+}
+
+/* Sets FMT's width to WIDTH (or the nearest width allowed by FMT's type) and
+ reduces its decimal places as necessary (if necessary) for that width. */
+void
+fmt_change_width (struct fmt_spec *fmt, int width, enum fmt_use use)
+{
+ fmt->w = width;
+ fmt_clamp_width (fmt, use);
+ fmt_clamp_decimals (fmt, use);
+}
+
+/* Sets FMT's decimal places to DECIMALS (or the nearest number of decimal
+ places allowed by FMT's type) and increases its width as necessary (if
+ necessary) for that number of decimal places. */
+void
+fmt_change_decimals (struct fmt_spec *fmt, int decimals, enum fmt_use use)
+{
+ fmt->d = decimals;
+ fmt_fix (fmt, use);
+}
\f
/* Describes a display format. */
struct fmt_desc
int i;
for (i = 0; i < FMT_NUMBER_OF_FORMATS; i++)
- if (!strcasecmp (name, get_fmt_desc (i)->name))
+ if (!c_strcasecmp (name, get_fmt_desc (i)->name))
{
*type = i;
return true;
return fmt_max_output_decimals (type, fmt_max_output_width (type)) > 0;
}
-/* Returns the minimum acceptable width for an input field
- formatted with the given TYPE. */
+/* Returns the minimum width of the given format TYPE for the given USE. */
int
-fmt_min_input_width (enum fmt_type type)
+fmt_min_width (enum fmt_type type, enum fmt_use use)
{
- return get_fmt_desc (type)->min_input_width;
+ return (use == FMT_FOR_INPUT
+ ? fmt_min_input_width (type)
+ : fmt_min_output_width (type));
}
-/* Returns the maximum acceptable width for an input field
- formatted with the given TYPE. */
+/* Returns the maximum width of the given format TYPE,
+ for input if FOR_INPUT is true,
+ for output otherwise. */
int
-fmt_max_input_width (enum fmt_type type)
+fmt_max_width (enum fmt_type type, enum fmt_use use UNUSED)
{
- return max_width (type);
-}
+ /* Maximum width is actually invariant of whether the format is
+ for input or output, so FOR_INPUT is unused. */
+ assert (is_fmt_type (type));
+ switch (type)
+ {
+ case FMT_P:
+ case FMT_PK:
+ case FMT_PIBHEX:
+ case FMT_RBHEX:
+ return 16;
-/* Returns the maximum number of decimal places allowed in an
- input field of the given TYPE and WIDTH. */
-int
-fmt_max_input_decimals (enum fmt_type type, int width)
-{
- assert (valid_width (type, width, true));
- return max_decimals (type, width, true);
-}
+ case FMT_IB:
+ case FMT_PIB:
+ case FMT_RB:
+ return 8;
-/* Returns the minimum acceptable width for an output field
- formatted with the given TYPE. */
-int
-fmt_min_output_width (enum fmt_type type)
-{
- return get_fmt_desc (type)->min_output_width;
-}
+ case FMT_A:
+ return MAX_STRING;
-/* Returns the maximum acceptable width for an output field
- formatted with the given TYPE. */
-int
-fmt_max_output_width (enum fmt_type type)
-{
- return max_width (type);
-}
+ case FMT_AHEX:
+ return 2 * MAX_STRING;
-/* Returns the maximum number of decimal places allowed in an
- output field of the given TYPE and WIDTH. */
-int
-fmt_max_output_decimals (enum fmt_type type, int width)
-{
- assert (valid_width (type, width, false));
- return max_decimals (type, width, false);
+ default:
+ return 40;
+ }
}
-/* Returns the width step for a field formatted with the given
- TYPE. Field width must be a multiple of the width step. */
+/* Returns the maximum number of decimal places allowed for the
+ given format TYPE with a width of WIDTH places, for the given USE. */
int
-fmt_step_width (enum fmt_type type)
+fmt_max_decimals (enum fmt_type type, int width, enum fmt_use use)
{
- return fmt_get_category (type) & FMT_CAT_HEXADECIMAL ? 2 : 1;
-}
+ int max_d;
-/* Returns true if TYPE is used for string fields,
- false if it is used for numeric fields. */
-bool
-fmt_is_string (enum fmt_type type)
-{
- return fmt_get_category (type) & FMT_CAT_STRING;
-}
+ switch (type)
+ {
+ case FMT_F:
+ case FMT_COMMA:
+ case FMT_DOT:
+ max_d = use == FMT_FOR_INPUT ? width : width - 1;
+ break;
-/* Returns true if TYPE is used for numeric fields,
- false if it is used for string fields. */
-bool
-fmt_is_numeric (enum fmt_type type)
-{
- return !fmt_is_string (type);
-}
+ case FMT_DOLLAR:
+ case FMT_PCT:
+ max_d = use == FMT_FOR_INPUT ? width : width - 2;
+ break;
-/* Returns the format TYPE's category.
- Each format type is in exactly one category,
- and each category's value is bitwise disjoint from every other
- category. Thus, the return value may be tested for equality
- or compared bitwise against a mask of FMT_CAT_* values. */
-enum fmt_category
-fmt_get_category (enum fmt_type type)
-{
- return get_fmt_desc (type)->category;
-}
-
-/* Returns the output format selected by default when TYPE is
- used as an input format. */
-enum fmt_type
-fmt_input_to_output (enum fmt_type type)
-{
- enum fmt_category category = fmt_get_category (type);
- return (category & FMT_CAT_STRING ? FMT_A
- : category & (FMT_CAT_BASIC | FMT_CAT_HEXADECIMAL) ? FMT_F
- : type);
-}
-
-/* Returns the SPSS format type corresponding to the given PSPP
- format type. */
-int
-fmt_to_io (enum fmt_type type)
-{
- return get_fmt_desc (type)->io;
-};
-
-/* Determines the PSPP format corresponding to the given SPSS
- format type. If successful, sets *FMT_TYPE to the PSPP format
- and returns true. On failure, return false. */
-bool
-fmt_from_io (int io, enum fmt_type *fmt_type)
-{
- enum fmt_type type;
-
- for (type = 0; type < FMT_NUMBER_OF_FORMATS; type++)
- if (get_fmt_desc (type)->io == io)
- {
- *fmt_type = type;
- return true;
- }
- return false;
-}
-
-/* Returns true if TYPE may be used as an input format,
- false otherwise. */
-bool
-fmt_usable_for_input (enum fmt_type type)
-{
- assert (is_fmt_type (type));
- return fmt_get_category (type) != FMT_CAT_CUSTOM;
-}
-
-/* For time and date formats, returns a template used for input
- and output. */
-const char *
-fmt_date_template (enum fmt_type type)
-{
- switch (type)
- {
- case FMT_DATE:
- return "dd-mmm-yy";
- case FMT_ADATE:
- return "mm/dd/yy";
- case FMT_EDATE:
- return "dd.mm.yy";
- case FMT_JDATE:
- return "yyddd";
- case FMT_SDATE:
- return "yy/mm/dd";
- case FMT_QYR:
- return "q Q yy";
- case FMT_MOYR:
- return "mmm yy";
- case FMT_WKYR:
- return "ww WK yy";
- case FMT_DATETIME:
- return "dd-mmm-yyyy HH:MM";
- case FMT_TIME:
- return "h:MM";
- case FMT_DTIME:
- return "D HH:MM";
- default:
- NOT_REACHED ();
- }
-}
-\f
-/* Returns true if TYPE is a valid format type,
- false otherwise. */
-static bool
-is_fmt_type (enum fmt_type type)
-{
- return type < FMT_NUMBER_OF_FORMATS;
-}
-
-/* Returns the minimum width of the given format TYPE,
- for input if FOR_INPUT is true,
- for output otherwise. */
-static int
-min_width (enum fmt_type type, bool for_input)
-{
- return for_input ? fmt_min_input_width (type) : fmt_min_output_width (type);
-}
-
-/* Returns the maximum width of the given format TYPE,
- which is invariant between input and output. */
-static int
-max_width (enum fmt_type type)
-{
- assert (is_fmt_type (type));
- switch (type)
- {
- case FMT_P:
- case FMT_PK:
- case FMT_PIBHEX:
- case FMT_RBHEX:
- return 16;
-
- case FMT_IB:
- case FMT_PIB:
- case FMT_RB:
- return 8;
-
- case FMT_A:
- return MAX_STRING;
-
- case FMT_AHEX:
- return 2 * MAX_STRING;
-
- default:
- return 40;
- }
-}
-
-/* Returns true if WIDTH is a valid width for the given format
- TYPE,
- for input if FOR_INPUT is true,
- for output otherwise. */
-static bool
-valid_width (enum fmt_type type, int width, bool for_input)
-{
- return (width >= min_width (type, for_input)
- && width <= max_width (type));
-}
-
-/* Returns the maximum number of decimal places allowed for the
- given format TYPE with a width of WIDTH places,
- for input if FOR_INPUT is true,
- for output otherwise. */
-static int
-max_decimals (enum fmt_type type, int width, bool for_input)
-{
- int max_d;
-
- switch (type)
- {
- case FMT_F:
- case FMT_COMMA:
- case FMT_DOT:
- max_d = for_input ? width : width - 1;
- break;
-
- case FMT_DOLLAR:
- case FMT_PCT:
- max_d = for_input ? width : width - 2;
- break;
-
- case FMT_E:
- max_d = for_input ? width : width - 7;
- break;
+ case FMT_E:
+ max_d = use == FMT_FOR_INPUT ? width : width - 7;
+ break;
case FMT_CCA:
case FMT_CCB:
case FMT_CCC:
case FMT_CCD:
case FMT_CCE:
- assert (!for_input);
+ assert (use == FMT_FOR_OUTPUT);
max_d = width - 1;
break;
max_d = width - 21;
break;
+ case FMT_YMDHMS:
+ max_d = width - 20;
+ break;
+
+ case FMT_MTIME:
+ max_d = width - 6;
+ break;
+
case FMT_TIME:
max_d = width - 9;
break;
return max_d;
}
-/* Returns the maximum number of decimal digits in an unsigned
- binary number that is BYTES bytes long. */
-static int
-max_digits_for_bytes (int bytes)
+/* Returns the minimum acceptable width for an input field
+ formatted with the given TYPE. */
+int
+fmt_min_input_width (enum fmt_type type)
{
- int map[8] = {3, 5, 8, 10, 13, 15, 17, 20};
- assert (bytes > 0 && bytes <= sizeof map / sizeof *map);
- return map[bytes - 1];
+ return get_fmt_desc (type)->min_input_width;
}
-\f
-static struct fmt_number_style *styles[FMT_NUMBER_OF_FORMATS];
-/* Creates and returns a new struct fmt_number_style,
- initializing all affixes to empty strings. */
-struct fmt_number_style *
-fmt_number_style_create (void)
+/* Returns the maximum acceptable width for an input field
+ formatted with the given TYPE. */
+int
+fmt_max_input_width (enum fmt_type type)
{
- struct fmt_number_style *style = xmalloc (sizeof *style);
- style->neg_prefix = ss_empty ();
- style->prefix = ss_empty ();
- style->suffix = ss_empty ();
- style->neg_suffix = ss_empty ();
- style->decimal = '.';
- style->grouping = 0;
- return style;
+ return fmt_max_width (type, FMT_FOR_INPUT);
}
-/* Destroys a struct fmt_number_style. */
-void
-fmt_number_style_destroy (struct fmt_number_style *style)
+/* Returns the maximum number of decimal places allowed in an
+ input field of the given TYPE and WIDTH. */
+int
+fmt_max_input_decimals (enum fmt_type type, int width)
{
- if (style != NULL)
- {
- ss_dealloc (&style->neg_prefix);
- ss_dealloc (&style->prefix);
- ss_dealloc (&style->suffix);
- ss_dealloc (&style->neg_suffix);
- free (style);
- }
+ assert (valid_width (type, width, true));
+ return fmt_max_decimals (type, width, FMT_FOR_INPUT);
}
-/* Returns the number formatting style associated with the given
- format TYPE. */
-const struct fmt_number_style *
-fmt_get_style (enum fmt_type type)
+/* Returns the minimum acceptable width for an output field
+ formatted with the given TYPE. */
+int
+fmt_min_output_width (enum fmt_type type)
{
- assert (is_fmt_type (type));
- assert (styles[type] != NULL);
- return styles[type];
+ return get_fmt_desc (type)->min_output_width;
}
-/* Sets STYLE as the number formatting style associated with the
- given format TYPE, transferring ownership of STYLE. */
-void
-fmt_set_style (enum fmt_type type, struct fmt_number_style *style)
+/* Returns the maximum acceptable width for an output field
+ formatted with the given TYPE. */
+int
+fmt_max_output_width (enum fmt_type type)
{
- assert (ss_length (style->neg_prefix) <= FMT_STYLE_AFFIX_MAX);
- assert (ss_length (style->prefix) <= FMT_STYLE_AFFIX_MAX);
- assert (ss_length (style->suffix) <= FMT_STYLE_AFFIX_MAX);
- assert (ss_length (style->neg_suffix) <= FMT_STYLE_AFFIX_MAX);
- assert (style->decimal == '.' || style->decimal == ',');
- assert (style->grouping != style->decimal
- && (style->grouping == '.' || style->grouping == ','
- || style->grouping == 0));
-
- assert (fmt_get_category (type) == FMT_CAT_CUSTOM);
- assert (styles[type] != NULL);
-
- fmt_number_style_destroy (styles[type]);
- styles[type] = style;
+ return fmt_max_width (type, FMT_FOR_OUTPUT);
}
-/* Returns the total width of the standard prefix and suffix for
- STYLE. */
+/* Returns the maximum number of decimal places allowed in an
+ output field of the given TYPE and WIDTH. */
int
-fmt_affix_width (const struct fmt_number_style *style)
+fmt_max_output_decimals (enum fmt_type type, int width)
{
- return ss_length (style->prefix) + ss_length (style->suffix);
+ assert (valid_width (type, width, false));
+ return fmt_max_decimals (type, width, FMT_FOR_OUTPUT);
}
-/* Returns the total width of the negative prefix and suffix for
- STYLE. */
+/* Returns the width step for a field formatted with the given
+ TYPE. Field width must be a multiple of the width step. */
int
-fmt_neg_affix_width (const struct fmt_number_style *style)
+fmt_step_width (enum fmt_type type)
{
- return ss_length (style->neg_prefix) + ss_length (style->neg_suffix);
+ return (fmt_get_category (type) == FMT_CAT_HEXADECIMAL || type == FMT_AHEX
+ ? 2 : 1);
}
-/* Returns the decimal point character for the given format
- TYPE. */
-int
-fmt_decimal_char (enum fmt_type type)
+/* Returns true if TYPE is used for string fields,
+ false if it is used for numeric fields. */
+bool
+fmt_is_string (enum fmt_type type)
+{
+ return fmt_get_category (type) == FMT_CAT_STRING;
+}
+
+/* Returns true if TYPE is used for numeric fields,
+ false if it is used for string fields. */
+bool
+fmt_is_numeric (enum fmt_type type)
+{
+ return !fmt_is_string (type);
+}
+
+/* Returns the format TYPE's category.
+ Each format type is in exactly one category,
+ and each category's value is bitwise disjoint from every other
+ category. Thus, the return value may be tested for equality
+ or compared bitwise against a mask of FMT_CAT_* values. */
+enum fmt_category
+fmt_get_category (enum fmt_type type)
{
- return fmt_get_style (type)->decimal;
+ return get_fmt_desc (type)->category;
}
-/* Returns the grouping character for the given format TYPE, or 0
- if the format type does not group digits. */
+/* Returns the output format selected by default when TYPE is
+ used as an input format. */
+enum fmt_type
+fmt_input_to_output (enum fmt_type type)
+{
+ switch (fmt_get_category (type))
+ {
+ case FMT_CAT_STRING:
+ return FMT_A;
+
+ case FMT_CAT_LEGACY:
+ case FMT_CAT_BINARY:
+ case FMT_CAT_HEXADECIMAL:
+ return FMT_F;
+
+ default:
+ return type;
+ }
+}
+
+/* Returns the SPSS format type corresponding to the given PSPP
+ format type. */
int
-fmt_grouping_char (enum fmt_type type)
+fmt_to_io (enum fmt_type type)
{
- return fmt_get_style (type)->grouping;
+ return get_fmt_desc (type)->io;
}
-/* Sets the number style for TYPE to have the given standard
- PREFIX and SUFFIX, "-" as prefix suffix, an empty negative
- suffix, DECIMAL as the decimal point character, and GROUPING
- as the grouping character. */
-static void
-set_style (enum fmt_type type,
- const char *prefix, const char *suffix,
- char decimal, char grouping)
+/* Determines the PSPP format corresponding to the given SPSS
+ format type. If successful, sets *FMT_TYPE to the PSPP format
+ and returns true. On failure, return false. */
+bool
+fmt_from_io (int io, enum fmt_type *fmt_type)
{
- struct fmt_number_style *style;
+ switch (io)
+ {
+#define FMT(NAME, METHOD, IMIN, OMIN, IO, CATEGORY) \
+ case IO: \
+ *fmt_type = FMT_##NAME; \
+ return true;
+#include "format.def"
+ default:
+ return false;
+ }
+}
- assert (is_fmt_type (type));
+/* Translate U32, which is in the form found in SAV and SPV files, into a
+ format specification, and stores the new specification in *F.
- fmt_number_style_destroy (styles[type]);
+ If LOOSE is false, checks that the format specification is appropriate as an
+ output format for a variable with the given WIDTH and reports an error if
+ not. If LOOSE is true, instead adjusts the format's width and decimals as
+ necessary to be suitable.
- style = styles[type] = fmt_number_style_create ();
- ss_alloc_substring (&style->neg_prefix, ss_cstr ("-"));
- ss_alloc_substring (&style->prefix, ss_cstr (prefix));
- ss_alloc_substring (&style->suffix, ss_cstr (suffix));
- style->decimal = decimal;
- style->grouping = grouping;
+ Return true if successful, false if there was an error.. */
+bool
+fmt_from_u32 (uint32_t u32, int width, bool loose, struct fmt_spec *f)
+{
+ uint8_t raw_type = u32 >> 16;
+ 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 ();
+
+ return ok;
}
-/* Sets the number style for TYPE as with set_style, but only if
- TYPE has not already been initialized. */
-static void
-init_style (enum fmt_type type,
- const char *prefix, const char *suffix,
- char decimal, char grouping)
+/* Returns true if TYPE may be used as an input format,
+ false otherwise. */
+bool
+fmt_usable_for_input (enum fmt_type type)
{
assert (is_fmt_type (type));
- if (styles[type] == NULL)
- set_style (type, prefix, suffix, decimal, grouping);
+ return fmt_get_category (type) != FMT_CAT_CUSTOM;
}
-/* Sets the decimal point character to DECIMAL. */
-void
-fmt_set_decimal (char decimal)
+/* For time and date formats, returns a template used for input and output in a
+ field of the given WIDTH.
+
+ WIDTH only affects whether a 2-digit year or a 4-digit year is used, that
+ is, whether the returned string contains "yy" or "yyyy", and whether seconds
+ are include, that is, whether the returned string contains ":SS". A caller
+ that doesn't care whether the returned string contains "yy" or "yyyy" or
+ ":SS" can just specify 0 to omit them. */
+const char *
+fmt_date_template (enum fmt_type type, int width)
{
- int grouping = decimal == '.' ? ',' : '.';
- assert (decimal == '.' || decimal == ',');
+ const char *s1, *s2;
+
+ switch (type)
+ {
+ case FMT_DATE:
+ s1 = "dd-mmm-yy";
+ s2 = "dd-mmm-yyyy";
+ break;
+
+ case FMT_ADATE:
+ s1 = "mm/dd/yy";
+ s2 = "mm/dd/yyyy";
+ break;
+
+ case FMT_EDATE:
+ s1 = "dd.mm.yy";
+ s2 = "dd.mm.yyyy";
+ break;
+
+ case FMT_JDATE:
+ s1 = "yyddd";
+ s2 = "yyyyddd";
+ break;
+
+ case FMT_SDATE:
+ s1 = "yy/mm/dd";
+ s2 = "yyyy/mm/dd";
+ break;
+
+ case FMT_QYR:
+ s1 = "q Q yy";
+ s2 = "q Q yyyy";
+ break;
+
+ case FMT_MOYR:
+ s1 = "mmm yy";
+ s2 = "mmm yyyy";
+ break;
+
+ case FMT_WKYR:
+ s1 = "ww WK yy";
+ s2 = "ww WK yyyy";
+ break;
+
+ case FMT_DATETIME:
+ s1 = "dd-mmm-yyyy HH:MM";
+ s2 = "dd-mmm-yyyy HH:MM:SS";
+ break;
- set_style (FMT_F, "", "", decimal, 0);
- set_style (FMT_E, "", "", decimal, 0);
- set_style (FMT_COMMA, "", "", decimal, grouping);
- set_style (FMT_DOT, "", "", grouping, decimal);
- set_style (FMT_DOLLAR, "$", "", decimal, grouping);
- set_style (FMT_PCT, "", "%", decimal, 0);
+ case FMT_YMDHMS:
+ s1 = "yyyy-mm-dd HH:MM";
+ s2 = "yyyy-mm-dd HH:MM:SS";
+ break;
+
+ case FMT_MTIME:
+ s1 = "MM";
+ s2 = "MM:SS";
+ break;
+
+ case FMT_TIME:
+ s1 = "HH:MM";
+ s2 = "HH:MM:SS";
+ break;
+
+ case FMT_DTIME:
+ s1 = "D HH:MM";
+ s2 = "D HH:MM:SS";
+ break;
+
+ default:
+ NOT_REACHED ();
+ }
- init_style (FMT_CCA, "", "", decimal, grouping);
- init_style (FMT_CCB, "", "", decimal, grouping);
- init_style (FMT_CCC, "", "", decimal, grouping);
- init_style (FMT_CCD, "", "", decimal, grouping);
- init_style (FMT_CCE, "", "", decimal, grouping);
+ return width >= strlen (s2) ? s2 : s1;
+}
+
+/* Returns a string representing the format TYPE for use in a GUI dialog. */
+const char *
+fmt_gui_name (enum fmt_type type)
+{
+ switch (type)
+ {
+ case FMT_F:
+ return _("Numeric");
+
+ case FMT_COMMA:
+ return _("Comma");
+
+ case FMT_DOT:
+ return _("Dot");
+
+ case FMT_E:
+ return _("Scientific");
+
+ case FMT_DATE:
+ case FMT_EDATE:
+ case FMT_SDATE:
+ case FMT_ADATE:
+ case FMT_JDATE:
+ case FMT_QYR:
+ case FMT_MOYR:
+ case FMT_WKYR:
+ case FMT_DATETIME:
+ case FMT_YMDHMS:
+ case FMT_MTIME:
+ case FMT_TIME:
+ case FMT_DTIME:
+ case FMT_WKDAY:
+ case FMT_MONTH:
+ return _("Date");
+
+ case FMT_DOLLAR:
+ return _("Dollar");
+
+ case FMT_CCA:
+ case FMT_CCB:
+ case FMT_CCC:
+ case FMT_CCD:
+ case FMT_CCE:
+ return _("Custom");
+
+ case FMT_A:
+ return _("String");
+
+ default:
+ return fmt_name (type);
+ }
}
\f
-/* Returns true if M is a valid variable measurement level,
+/* Returns true if TYPE is a valid format type,
false otherwise. */
bool
-measure_is_valid (enum measure m)
+is_fmt_type (enum fmt_type type)
{
- return m > 0 && m < n_MEASURES;
+ return type < FMT_NUMBER_OF_FORMATS;
}
-/* Returns true if A is a valid alignment,
- false otherwise. */
-bool
-alignment_is_valid (enum alignment a)
+/* Returns true if WIDTH is a valid width for the given format
+ TYPE, for the given USE. */
+static bool
+valid_width (enum fmt_type type, int width, enum fmt_use use)
{
- return a < n_ALIGN;
+ return (width >= fmt_min_width (type, use)
+ && width <= fmt_max_width (type, use));
+}
+
+/* Returns the maximum number of decimal digits in an unsigned
+ binary number that is BYTES bytes long. */
+static int
+max_digits_for_bytes (int bytes)
+{
+ int map[8] = {3, 5, 8, 10, 13, 15, 17, 20};
+ assert (bytes > 0 && bytes <= sizeof map / sizeof *map);
+ return map[bytes - 1];
+}
+
+/* Clamp FMT's width to the range and values allowed by FMT's type. */
+static void
+fmt_clamp_width (struct fmt_spec *fmt, enum fmt_use use)
+{
+ unsigned int step;
+ int min_w, max_w;
+
+ min_w = fmt_min_width (fmt->type, use);
+ max_w = fmt_max_width (fmt->type, use);
+ if (fmt->w < min_w)
+ fmt->w = min_w;
+ else if (fmt->w > max_w)
+ fmt->w = max_w;
+
+ /* Round width to step. */
+ step = fmt_step_width (fmt->type);
+ fmt->w = ROUND_DOWN (fmt->w, step);
+}
+
+/* Clamp FMT's decimal places to the range allowed by FMT's type and width. */
+static void
+fmt_clamp_decimals (struct fmt_spec *fmt, enum fmt_use use)
+{
+ int max_d = fmt_max_decimals (fmt->type, fmt->w, use);
+ if (fmt->d > max_d)
+ fmt->d = max_d;
}
\f
+static struct fmt_affix
+fmt_affix_clone (const struct fmt_affix *old)
+{
+ return (struct fmt_affix) {
+ .s = xstrdup_if_nonnull (old->s),
+ .width = old->width,
+ };
+}
+
+/* Frees data in AFFIX. */
+static void
+fmt_affix_free (struct fmt_affix *affix)
+{
+ if (affix->s)
+ free (affix->s);
+}
+
+/* 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)
+{
+ 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 };
+}
+
+struct fmt_number_style *
+fmt_number_style_from_string (const char *s)
+{
+ 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,
+ .include_leading_zero = false,
+ .extra_bytes = extra_bytes,
+ };
+ return style;
+}
+
+static void
+format_cc (struct string *out, const char *in, char grouping)
+{
+ while (*in != '\0')
+ {
+ char c = *in++;
+ if (c == grouping || c == '\'')
+ ds_put_byte (out, '\'');
+ else if (c == '"')
+ ds_put_byte (out, '"');
+ ds_put_byte (out, c);
+ }
+}
+
+char *
+fmt_number_style_to_string (const struct fmt_number_style *cc)
+{
+ struct string out = DS_EMPTY_INITIALIZER;
+ format_cc (&out, cc->neg_prefix.s, cc->grouping);
+ ds_put_byte (&out, cc->grouping);
+ format_cc (&out, cc->prefix.s, cc->grouping);
+ ds_put_byte (&out, cc->grouping);
+ format_cc (&out, cc->suffix.s, cc->grouping);
+ ds_put_byte (&out, cc->grouping);
+ format_cc (&out, cc->neg_suffix.s, cc->grouping);
+ return ds_steal_cstr (&out);
+}
+
+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. */
+void
+fmt_number_style_destroy (struct fmt_number_style *style)
+{
+ if (style != NULL)
+ {
+ fmt_affix_free (&style->neg_prefix);
+ fmt_affix_free (&style->prefix);
+ fmt_affix_free (&style->suffix);
+ fmt_affix_free (&style->neg_suffix);
+ free (style);
+ }
+}
+
+/* Returns the total width of the standard prefix and suffix for STYLE, in
+ display columns (e.g. as returned by u8_strwidth()). */
+int
+fmt_affix_width (const struct fmt_number_style *style)
+{
+ return style->prefix.width + style->suffix.width;
+}
+
+/* Returns the total width of the negative prefix and suffix for STYLE, in
+ display columns (e.g. as returned by u8_strwidth()). */
+int
+fmt_neg_affix_width (const struct fmt_number_style *style)
+{
+ return style->neg_prefix.width + style->neg_suffix.width;
+}
+
/* Returns the struct fmt_desc for the given format TYPE. */
static const struct fmt_desc *
get_fmt_desc (enum fmt_type type)
assert (is_fmt_type (type));
return &formats[type];
}
+
+const struct fmt_spec F_8_0 = { .type = FMT_F, .w = 8, .d = 0 };
+const struct fmt_spec F_8_2 = { .type = FMT_F, .w = 8, .d = 2 };
+const struct fmt_spec F_4_3 = { .type = FMT_F, .w = 4, .d = 3 };
+const struct fmt_spec F_5_1 = { .type = FMT_F, .w = 5, .d = 1 };