-/* PSPP - computes sample statistics.
- Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
- Written by Ben Pfaff <blp@gnu.org>.
+/* PSPP - a program for statistical analysis.
+ Copyright (C) 1997-9, 2000, 2006 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 "format.h"
+
#include <ctype.h>
-#include "message.h"
#include <stdlib.h>
-#include "misc.h"
-#include "identifier.h"
-#include "str.h"
-#include "variable.h"
+
+#include <data/identifier.h>
+#include <data/settings.h>
+#include <data/value.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 "gettext.h"
#define _(msgid) gettext (msgid)
-#define DEFFMT(LABEL, NAME, N_ARGS, IMIN_W, IMAX_W, OMIN_W, OMAX_W, CAT, \
- OUTPUT, SPSS_FMT) \
- {NAME, N_ARGS, IMIN_W, IMAX_W, OMIN_W, OMAX_W, CAT, OUTPUT, SPSS_FMT},
-struct fmt_desc formats[FMT_NUMBER_OF_FORMATS + 1] =
+
+
+bool is_fmt_type (enum fmt_type);
+
+static bool valid_width (enum fmt_type, int width, bool for_input);
+
+static int max_digits_for_bytes (int bytes);
+
+void fmt_number_style_init (struct fmt_number_style *style);
+
+
+/* Initialize the format module. */
+struct fmt_number_style *
+fmt_create (void)
{
-#include "format.def"
- {"", -1, -1, -1, -1, -1, 0000, -1, -1},
-};
+ struct fmt_number_style *styles =
+ xcalloc (FMT_NUMBER_OF_FORMATS, sizeof (*styles));
-/* Common formats. */
-const struct fmt_spec f8_2 = {FMT_F, 8, 2};
+ int t;
+ for (t = 0 ; t < FMT_NUMBER_OF_FORMATS ; ++t )
+ fmt_number_style_init (&styles[t]);
-/* Converts F to its string representation (for instance, "F8.2") and
- returns a pointer to a static buffer containing that string. */
-char *
-fmt_to_string (const struct fmt_spec *f)
+ fmt_set_decimal (styles, '.');
+
+ return styles;
+}
+
+
+/* Deinitialize the format module. */
+void
+fmt_done (struct fmt_number_style *styles)
{
- static char buf[32];
+ int t;
+ for (t = 0 ; t < FMT_NUMBER_OF_FORMATS ; ++t )
+ fmt_number_style_destroy (&styles[t]);
- if (formats[f->type].n_args >= 2)
- sprintf (buf, "%s%d.%d", formats[f->type].name, f->w, f->d);
- else
- sprintf (buf, "%s%d", formats[f->type].name, f->w);
- return buf;
+ free (styles);
}
-/* Does checks in common betwen check_input_specifier() and
- check_output_specifier() and returns true if so. Otherwise,
- emits an error message (if EMIT_ERROR is nonzero) and returns
- false. */
-static bool
-check_common_specifier (const struct fmt_spec *spec, bool emit_error)
+/* 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;
+ assert (fmt_check_input (&f));
+ return f;
+}
+
+/* Returns an output format specification with type TYPE, width
+ W, and D decimals. */
+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;
+ 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)
+{
+ struct fmt_spec output;
+
+ assert (fmt_check_input (input));
+
+ output.type = fmt_input_to_output (input->type);
+ output.w = input->w;
+ if (output.w > fmt_max_output_width (output.type))
+ output.w = fmt_max_output_width (output.type);
+ else if (output.w < fmt_min_output_width (output.type))
+ output.w = fmt_min_output_width (output.type);
+ output.d = input->d;
+
+ switch (input->type)
+ {
+ case FMT_Z:
+ output.w++;
+ if (output.d > 0)
+ output.w++;
+ break;
+
+ case FMT_F:
+ case FMT_COMMA:
+ case FMT_DOT:
+ case FMT_DOLLAR:
+ case FMT_PCT:
+ {
+ const struct fmt_number_style *style =
+ settings_get_style (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;
+ if (output.d > 0)
+ output.w++;
+ }
+ break;
+
+ case FMT_N:
+ if (output.d > 0)
+ output.w++;
+ break;
+
+ case FMT_E:
+ output.d = MAX (input->d, 3);
+ output.w = MAX (input->w, output.d + 7);
+ break;
+
+ case FMT_PIBHEX:
+ output.w = max_digits_for_bytes (input->w / 2) + 1;
+ break;
+
+ case FMT_RB:
+ case FMT_RBHEX:
+ output.w = 8;
+ output.d = 2;
+ break;
+
+ case FMT_P:
+ case FMT_PK:
+ output.w = 2 * input->w + (input->d > 0);
+ break;
+
+ case FMT_IB:
+ case FMT_PIB:
+ output.w = max_digits_for_bytes (input->w) + 1;
+ if (output.d > 0)
+ output.w++;
+ break;
+
+ case FMT_CCA:
+ case FMT_CCB:
+ case FMT_CCC:
+ case FMT_CCD:
+ case FMT_CCE:
+ NOT_REACHED ();
+
+ case FMT_A:
+ break;
+
+ case FMT_AHEX:
+ output.w = input->w / 2;
+ break;
+
+ 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_TIME:
+ case FMT_DTIME:
+ case FMT_DATETIME:
+ case FMT_WKDAY:
+ case FMT_MONTH:
+ break;
+
+ default:
+ NOT_REACHED ();
+ }
+
+ if (output.w > fmt_max_output_width (output.type))
+ output.w = fmt_max_output_width (output.type);
+
+ assert (fmt_check_output (&output));
+ return output;
+}
+
+/* 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)
{
- struct fmt_desc *f ;
- char *str;
+ return (width == 0
+ ? fmt_for_output (FMT_F, 8, 2)
+ : fmt_for_output (FMT_A, width, 0));
+}
+
+/* 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)
+{
+ 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 ( spec->type > FMT_NUMBER_OF_FORMATS )
+ if (for_input && !fmt_usable_for_input (spec->type))
{
- if (emit_error)
- msg (SE, _("Format specifies a bad type (%d)"), spec->type);
-
+ msg (SE, _("Format %s may not be used for input."), str);
return false;
}
- f = &formats[spec->type];
- str = fmt_to_string (spec);
+ 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;
+ }
- if ((f->cat & FCAT_EVEN_WIDTH) && spec->w % 2)
+ min_w = fmt_min_width (spec->type, for_input);
+ max_w = fmt_max_width (spec->type, for_input);
+ if (spec->w < min_w || spec->w > max_w)
{
- if (emit_error)
- msg (SE, _("Format %s specifies an odd width %d, but "
- "an even width is required."),
- str, spec->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;
}
- if (f->n_args > 1 && (spec->d < 0 || spec->d > 16))
+
+ max_d = fmt_max_decimals (spec->type, spec->w, for_input);
+ if (!fmt_takes_decimals (spec->type) && spec->d != 0)
{
- if (emit_error)
- msg (SE, _("Format %s specifies a bad number of "
- "implied decimal places %d. Input format %s allows "
- "up to 16 implied decimal places."), str, spec->d, f->name);
+ 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;
}
+ 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);
+ 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 false;
+ }
+
return true;
}
/* Checks whether SPEC is valid as an input format and returns
- nonzero if so. Otherwise, emits an error message (if
- EMIT_ERROR is nonzero) and returns zero. */
-int
-check_input_specifier (const struct fmt_spec *spec, int emit_error)
+ nonzero if so. Otherwise, emits an error message and returns
+ zero. */
+bool
+fmt_check_input (const struct fmt_spec *spec)
{
- struct fmt_desc *f ;
- char *str ;
-
- if (!check_common_specifier (spec, emit_error))
- return false;
-
- f = &formats[spec->type];
- str = fmt_to_string (spec);
+ return fmt_check (spec, true);
+}
+/* Checks whether SPEC is valid as an output format and returns
+ true if so. Otherwise, emits an error message and returns false. */
+bool
+fmt_check_output (const struct fmt_spec *spec)
+{
+ return fmt_check (spec, false);
+}
- if (spec->type == FMT_X)
- return 1;
- if (f->cat & FCAT_OUTPUT_ONLY)
- {
- if (emit_error)
- msg (SE, _("Format %s may not be used for input."), f->name);
- return 0;
- }
- if (spec->w < f->Imin_w || spec->w > f->Imax_w)
+/* 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. */
+bool
+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))
{
- if (emit_error)
- msg (SE, _("Input format %s specifies a bad width %d. "
- "Format %s requires a width between %d and %d."),
- str, spec->w, f->name, f->Imin_w, f->Imax_w);
- return 0;
+ char str[FMT_STRING_LEN_MAX + 1];
+ msg (SE, _("%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 false;
}
- if ((spec->type == FMT_F || spec->type == FMT_COMMA
- || spec->type == FMT_DOLLAR)
- && spec->d > spec->w)
+ return true;
+}
+
+/* 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. */
+bool
+fmt_check_width_compat (const struct fmt_spec *format, int width)
+{
+ if (!fmt_check_type_compat (format, val_type_from_width (width)))
+ return false;
+ if (fmt_var_width (format) != width)
{
- if (emit_error)
- msg (SE, _("Input format %s is invalid because it specifies more "
- "decimal places than the field width."), str);
- return 0;
+ 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 1;
+ return true;
}
-/* Checks whether SPEC is valid as an output format and returns
- nonzero if so. Otherwise, emits an error message (if
- EMIT_ERROR is nonzero) and returns zero. */
+/* Returns the width corresponding to FORMAT. The return value
+ is the width of the `union value's required by FORMAT. */
int
-check_output_specifier (const struct fmt_spec *spec, int emit_error)
+fmt_var_width (const struct fmt_spec *format)
{
- struct fmt_desc *f;
- char *str ;
+ return (format->type == FMT_AHEX ? format->w / 2
+ : format->type == FMT_A ? format->w
+ : 0);
+}
- if (!check_common_specifier (spec, emit_error))
- return false;
+/* Converts F to its string representation (for instance, "F8.2")
+ in BUFFER. Returns BUFFER.
+
+ If F has decimals, they are included in the output string,
+ even if F's format type does not allow decimals, to allow
+ accurately presenting incorrect formats to the user. */
+char *
+fmt_to_string (const struct fmt_spec *f, char buffer[FMT_STRING_LEN_MAX + 1])
+{
+ if (fmt_takes_decimals (f->type) || f->d > 0)
+ snprintf (buffer, FMT_STRING_LEN_MAX + 1,
+ "%s%d.%d", fmt_name (f->type), f->w, f->d);
+ else
+ snprintf (buffer, FMT_STRING_LEN_MAX + 1,
+ "%s%d", fmt_name (f->type), f->w);
+ return buffer;
+}
- f = &formats[spec->type];
- str = fmt_to_string (spec);
+/* 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;
+}
- if (spec->type == FMT_X)
- return 1;
- if (spec->w < f->Omin_w || spec->w > f->Omax_w)
+/* Adjusts FMT to be valid for a value of the given WIDTH. */
+void
+fmt_resize (struct fmt_spec *fmt, int width)
+{
+ if ((width > 0) != fmt_is_string (fmt->type))
{
- if (emit_error)
- msg (SE, _("Output format %s specifies a bad width %d. "
- "Format %s requires a width between %d and %d."),
- str, spec->w, f->name, f->Omin_w, f->Omax_w);
- return 0;
+ /* Changed from numeric to string or vice versa. Set to
+ default format for new width. */
+ *fmt = fmt_default_for_width (width);
}
- if ((spec->type == FMT_F || spec->type == FMT_COMMA
- || spec->type == FMT_DOLLAR)
- && spec->d >= spec->w)
+ else if (width > 0)
{
- if (emit_error)
- msg (SE, _("Output format %s is invalid because it specifies as "
- "many decimal places as the field width, which "
- "fails to allow space for a decimal point. "
- "Try %s%d.%d instead."),
- str, f->name, spec->d + 1, spec->d);
- return 0;
+ /* Changed width of string. Preserve format type, adjust
+ width. */
+ fmt->w = fmt->type == FMT_AHEX ? width * 2 : width;
+ }
+ else
+ {
+ /* Still numeric. */
}
- return 1;
}
-/* Checks that FORMAT is appropriate for a variable of the given
- TYPE and returns true if so. Otherwise returns false and (if
- EMIT_ERROR is true) emits an error message. */
-bool
-check_specifier_type (const struct fmt_spec *format,
- int type, bool emit_error)
+/* Adjusts FMT's width and decimal places to be valid for an
+ input format (if FOR_INPUT) or an output format (if
+ !FOR_INPUT). */
+void
+fmt_fix (struct fmt_spec *fmt, bool for_input)
{
- const struct fmt_desc *f = &formats[format->type];
- assert (type == NUMERIC || type == ALPHA);
- if ((type == ALPHA) != ((f->cat & FCAT_STRING) != 0))
+ int min_w, max_w;
+ int max_d;
+
+ /* Clamp width to those allowed by format. */
+ min_w = fmt_min_width (fmt->type, for_input);
+ max_w = fmt_max_width (fmt->type, for_input);
+ if (fmt->w < min_w)
+ fmt->w = min_w;
+ else if (fmt->w > max_w)
+ fmt->w = max_w;
+
+ /* First, 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, for_input))
{
- if (emit_error)
- msg (SE, _("%s variables are not compatible with %s format %s."),
- type == ALPHA ? _("String") : _("Numeric"),
- type == ALPHA ? _("numeric") : _("string"),
- fmt_to_string (format));
- return false;
+ int w;
+ for (w = fmt->w; w <= max_w; w++)
+ if (fmt_max_decimals (fmt->type, w, for_input) >= fmt->d)
+ {
+ fmt->w = w;
+ break;
+ }
}
- return true;
+
+ /* Clamp decimals to those allowed by format and width. */
+ max_d = fmt_max_decimals (fmt->type, fmt->w, for_input);
+ if (fmt->d < 0)
+ fmt->d = 0;
+ else if (fmt->d > max_d)
+ fmt->d = max_d;
}
-
-/* Checks that FORMAT is appropriate for a variable of the given
- WIDTH and returns true if so. Otherwise returns false and (if
- EMIT_ERROR is true) emits an error message. */
+
+/* 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, true);
+}
+
+/* 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, false);
+}
+\f
+/* Describes a display format. */
+struct fmt_desc
+ {
+ char name[9];
+ int min_input_width, min_output_width;
+ int io;
+ enum fmt_category category;
+ };
+
+static const struct fmt_desc *get_fmt_desc (enum fmt_type type);
+
+/* Returns the name of the given format TYPE. */
+const char *
+fmt_name (enum fmt_type type)
+{
+ return get_fmt_desc (type)->name;
+}
+
+/* Tries to parse NAME as a format type.
+ If successful, stores the type in *TYPE and returns true.
+ On failure, returns false. */
bool
-check_specifier_width (const struct fmt_spec *format,
- int width, bool emit_error)
+fmt_from_name (const char *name, enum fmt_type *type)
{
- if (!check_specifier_type (format, width != 0 ? ALPHA : NUMERIC, emit_error))
- return false;
- if (get_format_var_width (format) != width)
+ int i;
+
+ for (i = 0; i < FMT_NUMBER_OF_FORMATS; i++)
+ if (!strcasecmp (name, get_fmt_desc (i)->name))
+ {
+ *type = i;
+ return true;
+ }
+ return false;
+}
+
+/* Returns true if TYPE accepts decimal places,
+ false otherwise. */
+bool
+fmt_takes_decimals (enum fmt_type type)
+{
+ return fmt_max_output_decimals (type, fmt_max_output_width (type)) > 0;
+}
+
+/* Returns the minimum width of the given format TYPE,
+ for input if FOR_INPUT is true,
+ for output otherwise. */
+int
+fmt_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,
+ for input if FOR_INPUT is true,
+ for output otherwise. */
+int
+fmt_max_width (enum fmt_type type, bool for_input UNUSED)
+{
+ /* 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)
{
- if (emit_error)
- msg (SE, _("String variable with width %d not compatible with "
- "format %s."),
- width, fmt_to_string (format));
- return false;
+ 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;
}
- return true;
}
-/* Converts input format specifier INPUT into output format
- specifier OUTPUT. */
-void
-convert_fmt_ItoO (const struct fmt_spec *input, struct fmt_spec *output)
+/* 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. */
+int
+fmt_max_decimals (enum fmt_type type, int width, bool for_input)
{
- assert (check_input_specifier (input, 0));
-
- output->type = formats[input->type].output;
- output->w = input->w;
- if (output->w > formats[output->type].Omax_w)
- output->w = formats[output->type].Omax_w;
- output->d = input->d;
+ int max_d;
- switch (input->type)
+ switch (type)
{
case FMT_F:
- case FMT_N:
- if (output->d > 0)
- output->w++;
- break;
- case FMT_E:
- output->w = max (max (input->w, input->d+7), 10);
- output->d = max (input->d, 3);
- break;
case FMT_COMMA:
case FMT_DOT:
- /* nothing is necessary */
+ max_d = for_input ? width : width - 1;
break;
+
case FMT_DOLLAR:
case FMT_PCT:
- if (output->w < 2)
- output->w = 2;
+ max_d = for_input ? width : width - 2;
break;
- case FMT_PIBHEX:
- {
- static const int map[] = {4, 6, 9, 11, 14, 16, 18, 21};
- assert (input->w % 2 == 0 && input->w >= 2 && input->w <= 16);
- output->w = map[input->w / 2 - 1];
- break;
- }
- case FMT_RBHEX:
- output->w = 8, output->d = 2; /* FIXME */
- break;
- case FMT_IB:
- case FMT_PIB:
- case FMT_P:
- case FMT_PK:
- case FMT_RB:
- if (input->d < 1)
- output->w = 8, output->d = 2;
- else
- output->w = 9 + input->d;
+
+ case FMT_E:
+ max_d = for_input ? width : width - 7;
break;
+
case FMT_CCA:
case FMT_CCB:
case FMT_CCC:
case FMT_CCD:
case FMT_CCE:
- assert (0);
+ assert (!for_input);
+ max_d = width - 1;
+ break;
+
+ case FMT_N:
case FMT_Z:
- case FMT_A:
- /* nothing is necessary */
+ max_d = width;
break;
- case FMT_AHEX:
- output->w = input->w / 2;
+
+ case FMT_P:
+ max_d = width * 2 - 1;
+ break;
+
+ case FMT_PK:
+ max_d = width * 2;
+ break;
+
+ case FMT_IB:
+ case FMT_PIB:
+ max_d = max_digits_for_bytes (width);
+ break;
+
+ case FMT_PIBHEX:
+ max_d = 0;
+ break;
+
+ case FMT_RB:
+ case FMT_RBHEX:
+ max_d = 16;
break;
+
case FMT_DATE:
- case FMT_EDATE:
- case FMT_SDATE:
case FMT_ADATE:
+ case FMT_EDATE:
case FMT_JDATE:
- /* nothing is necessary */
- break;
+ case FMT_SDATE:
case FMT_QYR:
- if (output->w < 6)
- output->w = 6;
- break;
case FMT_MOYR:
- /* nothing is necessary */
- break;
case FMT_WKYR:
- if (output->w < 8)
- output->w = 8;
+ max_d = 0;
+ break;
+
+ case FMT_DATETIME:
+ max_d = width - 21;
break;
+
case FMT_TIME:
+ max_d = width - 9;
+ break;
+
case FMT_DTIME:
- case FMT_DATETIME:
+ max_d = width - 12;
+ break;
+
case FMT_WKDAY:
case FMT_MONTH:
- /* nothing is necessary */
+ case FMT_A:
+ case FMT_AHEX:
+ max_d = 0;
break;
+
default:
- assert (0);
+ NOT_REACHED ();
}
- assert (check_output_specifier (output, 0));
+ if (max_d < 0)
+ max_d = 0;
+ else if (max_d > 16)
+ max_d = 16;
+ return max_d;
}
-/* 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 minimum acceptable width for an input field
+ formatted with the given TYPE. */
int
-get_format_var_width (const struct fmt_spec *spec)
+fmt_min_input_width (enum fmt_type type)
{
- if (spec->type == FMT_AHEX)
- return spec->w / 2;
- else if (spec->type == FMT_A)
- return spec->w;
- else
- return 0;
+ return get_fmt_desc (type)->min_input_width;
}
-/* Returns the PSPP format corresponding to the given SPSS
- format. */
+/* Returns the maximum acceptable width for an input field
+ formatted with the given TYPE. */
int
-translate_fmt (int spss)
+fmt_max_input_width (enum fmt_type type)
{
- int type;
-
- for (type = 0; type < FMT_NUMBER_OF_FORMATS; type++)
- if (formats[type].spss == spss)
+ return fmt_max_width (type, true);
+}
+
+/* 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 fmt_max_decimals (type, width, true);
+}
+
+/* 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;
+}
+
+/* Returns the maximum acceptable width for an output field
+ formatted with the given TYPE. */
+int
+fmt_max_output_width (enum fmt_type type)
+{
+ return fmt_max_width (type, false);
+}
+
+/* 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 fmt_max_decimals (type, width, false);
+}
+
+/* Returns the width step for a field formatted with the given
+ TYPE. Field width must be a multiple of the width step. */
+int
+fmt_step_width (enum fmt_type type)
+{
+ return (fmt_get_category (type) == FMT_CAT_HEXADECIMAL || type == FMT_AHEX
+ ? 2 : 1);
+}
+
+/* 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 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)
+{
+ 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;
- return -1;
+ }
}
-/* Returns an input format specification with type TYPE, width W,
- and D decimals. */
-struct fmt_spec
-make_input_format (int type, int w, int d)
+/* Returns the SPSS format type corresponding to the given PSPP
+ format type. */
+int
+fmt_to_io (enum fmt_type type)
{
- struct fmt_spec f;
- f.type = type;
- f.w = w;
- f.d = d;
- assert (check_input_specifier (&f, 0));
- return f;
+ return get_fmt_desc (type)->io;
}
-/* Returns an output format specification with type TYPE, width
- W, and D decimals. */
-struct fmt_spec
-make_output_format (int type, int w, int d)
+/* 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_spec f;
- f.type = type;
- f.w = w;
- f.d = d;
- assert (check_output_specifier (&f, 0));
- return f;
+ 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 "mmmXyy";
+ 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. */
+bool
+is_fmt_type (enum fmt_type type)
+{
+ return type < FMT_NUMBER_OF_FORMATS;
+}
+
+/* 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 >= fmt_min_width (type, for_input)
+ && width <= fmt_max_width (type, for_input));
+}
+
+/* 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];
+}
+\f
+
+
+void
+fmt_number_style_init (struct fmt_number_style *style)
+{
+ style->neg_prefix = ss_empty ();
+ style->prefix = ss_empty ();
+ style->suffix = ss_empty ();
+ style->neg_suffix = ss_empty ();
+ style->decimal = '.';
+ style->grouping = 0;
+}
+
+
+/* Destroys a struct fmt_number_style. */
+void
+fmt_number_style_destroy (struct fmt_number_style *style)
+{
+ if (style != NULL)
+ {
+ ss_dealloc (&style->neg_prefix);
+ ss_dealloc (&style->prefix);
+ ss_dealloc (&style->suffix);
+ ss_dealloc (&style->neg_suffix);
+ }
+}
+
+/* Returns the number formatting style associated with the given
+ format TYPE. */
+const struct fmt_number_style *
+fmt_get_style (const struct fmt_number_style *styles, enum fmt_type type)
+{
+ assert (is_fmt_type (type));
+ return &styles[type];
+}
+
+
+/* Checks that style is STYLE sane */
+void
+fmt_check_style (const struct fmt_number_style *style)
+{
+ 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->grouping == ','
+ || style->grouping == 0);
+ assert (style->grouping != style->decimal);
+}
+
+
+/* Returns the total width of the standard prefix and suffix for
+ STYLE. */
+int
+fmt_affix_width (const struct fmt_number_style *style)
+{
+ return ss_length (style->prefix) + ss_length (style->suffix);
+}
+
+/* Returns the total width of the negative prefix and suffix for
+ STYLE. */
+int
+fmt_neg_affix_width (const struct fmt_number_style *style)
+{
+ return ss_length (style->neg_prefix) + ss_length (style->neg_suffix);
+}
+
+
+/* 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 (struct fmt_number_style *styles, enum fmt_type type,
+ const char *prefix, const char *suffix,
+ char decimal, char grouping)
+{
+ struct fmt_number_style *style;
+
+ assert (is_fmt_type (type));
+
+ style = &styles[type] ;
+
+ fmt_number_style_destroy (style);
+
+ 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;
+}
+
+/* Sets the decimal point character to DECIMAL. */
+void
+fmt_set_decimal (struct fmt_number_style *styles, char decimal)
+{
+ int grouping = decimal == '.' ? ',' : '.';
+ assert (decimal == '.' || decimal == ',');
+
+ set_style (styles, FMT_F, "", "", decimal, 0);
+ set_style (styles, FMT_E, "", "", decimal, 0);
+ set_style (styles, FMT_COMMA, "", "", decimal, grouping);
+ set_style (styles, FMT_DOT, "", "", grouping, decimal);
+ set_style (styles, FMT_DOLLAR, "$", "", decimal, grouping);
+ set_style (styles, FMT_PCT, "", "%", decimal, 0);
+}
+\f
+/* Returns the struct fmt_desc for the given format TYPE. */
+static const struct fmt_desc *
+get_fmt_desc (enum fmt_type type)
+{
+ static const struct fmt_desc formats[FMT_NUMBER_OF_FORMATS] =
+ {
+#define FMT(NAME, METHOD, IMIN, OMIN, IO, CATEGORY) \
+ {#NAME, IMIN, OMIN, IO, CATEGORY},
+#include "format.def"
+ };
+
+ assert (is_fmt_type (type));
+ return &formats[type];
}