#include <ctype.h>
#include <stdlib.h>
+#include <time.h>
#include <uniwidth.h>
#include "data/identifier.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)
-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);
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)
+struct fmt_settings
+fmt_settings_copy (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]);
-
+ 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)
{
- 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, 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 ('.', ','),
+ };
-/* 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;
+ static const struct fmt_number_style default_style = NS ("", "", '.', 0, false);
- assert (grouping == '.' || grouping == ',' || grouping == 0);
- assert (decimal == '.' || decimal == ',');
- assert (decimal != grouping);
+ 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];
+ }
- fmt_number_style_destroy (style);
+ 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;
+ }
- 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;
+ default:
+ return &default_style;
+ }
+}
- 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);
+static int
+default_epoch (void)
+{
+ static int epoch = 0;
+ if (!epoch)
+ {
+ time_t t = time (0);
+ struct tm *tm = localtime (&t);
+ epoch = (tm != NULL ? tm->tm_year + 1900 : 2000) - 69;
+ }
+ return epoch;
}
-/* Sets the decimal point character for the settings in S to DECIMAL.
+int
+fmt_settings_get_epoch (const struct fmt_settings *settings)
+{
+ return !settings->epoch ? default_epoch () : settings->epoch;
+}
- 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 == ',');
+ size_t idx = fmt_type_to_cc_index (type);
- 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, "-", "", "%", "");
+ 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;
- assert (fmt_check_input (&f));
+ 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;
- assert (fmt_check_output (&f));
+ 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 (struct fmt_spec input,
+ const struct fmt_settings *settings)
{
struct fmt_spec output;
assert (fmt_check_input (input));
- output.type = fmt_input_to_output (input->type);
- output.w = input->w;
+ 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;
+ output.d = input.d;
- switch (input->type)
+ switch (input.type)
{
case FMT_Z:
output.w++;
case FMT_PCT:
{
const struct fmt_number_style *style =
- settings_get_style (input->type);
+ 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;
+ 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_E:
- output.d = MAX (input->d, 3);
- output.w = MAX (input->w, output.d + 7);
+ 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;
+ output.w = max_digits_for_bytes (input.w / 2) + 1;
break;
case FMT_RB:
case FMT_P:
case FMT_PK:
- output.w = 2 * input->w + (input->d > 0);
+ output.w = 2 * input.w + (input.d > 0);
break;
case FMT_IB:
case FMT_PIB:
- output.w = max_digits_for_bytes (input->w) + 1;
+ output.w = max_digits_for_bytes (input.w) + 1;
if (output.d > 0)
output.w++;
break;
break;
case FMT_AHEX:
- output.w = input->w / 2;
+ output.w = input.w / 2;
break;
case FMT_DATE:
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 ();
}
if (output.w > fmt_max_output_width (output.type))
output.w = fmt_max_output_width (output.type);
- assert (fmt_check_output (&output));
+ assert (fmt_check_output (output));
return output;
}
: fmt_for_output (FMT_A, width, 0));
}
-/* Checks whether SPEC is valid for USE and returns nonzero if so.
- Otherwise, emits an error message and returns zero. */
-bool
-fmt_check (const struct fmt_spec *spec, enum fmt_use use)
+/* 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__ (struct fmt_spec spec, enum fmt_use use)
{
- const char *io_fmt;
char str[FMT_STRING_LEN_MAX + 1];
int min_w, max_w, max_d;
- assert (is_fmt_type (spec->type));
+ assert (is_fmt_type (spec.type));
fmt_to_string (spec, str);
- io_fmt = use == FMT_FOR_INPUT ? _("Input format") : _("Output format");
- if (use == FMT_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))
+ 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;
+ assert (fmt_step_width (spec.type) == 2);
+ 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 = fmt_min_width (spec->type, use);
- max_w = fmt_max_width (spec->type, use);
- if (spec->w < min_w || spec->w > max_w)
+ 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)
+ 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)
+ 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)
{
- 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 (max_d > 0)
+ return (use == FMT_FOR_INPUT
+ ? xasprintf (ngettext (
+ "Input format %s specifies %d decimal place, "
+ "but width %d allows at most %d decimals.",
+ "Input format %s specifies %d decimal places, "
+ "but width %d allows at most %d decimals.",
+ spec.d),
+ str, spec.d, spec.w, max_d)
+ : xasprintf (ngettext (
+ "Output format %s specifies %d decimal place, "
+ "but width %d allows at most %d decimals.",
+ "Output format %s specifies %d decimal places, "
+ "but width %d allows at most %d decimals.",
+ spec.d),
+ str, spec.d, spec.w, max_d));
+ else
+ return (use == FMT_FOR_INPUT
+ ? xasprintf (ngettext (
+ "Input format %s specifies %d decimal place, "
+ "but width %d does not allow for any decimals.",
+ "Input format %s specifies %d decimal places, "
+ "but width %d does not allow for any decimals.",
+ spec.d),
+ str, spec.d, spec.w)
+ : xasprintf (ngettext (
+ "Output format %s specifies %d decimal place, "
+ "but width %d does not allow for any decimals.",
+ "Output format %s specifies %d decimal places, "
+ "but width %d does not allow for any decimals.",
+ spec.d),
+ str, spec.d, spec.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;
- }
- else if (spec->d > max_d)
+ return NULL;
+}
+
+char *
+fmt_check_input__ (struct fmt_spec spec)
+{
+ return fmt_check__ (spec, FMT_FOR_INPUT);
+}
+
+char *
+fmt_check_output__ (struct fmt_spec spec)
+{
+ return fmt_check__ (spec, FMT_FOR_OUTPUT);
+}
+
+static bool
+error_to_bool (char *error)
+{
+ if (error)
{
- 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);
+ free (error);
return false;
}
+ else
+ return true;
+}
- return true;
+/* Returns true if SPEC is valid for USE, false otherwise. */
+bool
+fmt_check (struct fmt_spec spec, enum fmt_use 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)
+fmt_check_input (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)
+fmt_check_output (struct fmt_spec spec)
{
return fmt_check (spec, FMT_FOR_OUTPUT);
}
-/* 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)
+/* 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
+ caller must eventually free(). VARNAME is optional and only used in the
+ error message.*/
+char *
+fmt_check_type_compat__ (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))
+ if ((var_type == VAL_STRING) != (fmt_is_string (format.type) != 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;
+ 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 true;
+ 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. */
+/* Returns FORMAT is appropriate for a variable of the given VAR_TYPE and
+ returns true if so, otherwise false. */
bool
-fmt_check_width_compat (const struct fmt_spec *format, int width)
+fmt_check_type_compat (struct fmt_spec format, enum val_type var_type)
{
- if (!fmt_check_type_compat (format, val_type_from_width (width)))
- return false;
+ 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
+ caller must eventually free(). VARNAME is optional and only used in the
+ error message. */
+char *
+fmt_check_width_compat__ (struct fmt_spec format, const char *varname,
+ int 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];
- msg (SE, _("String variable with width %d is not compatible with "
- "format %s."),
- width, fmt_to_string (format, str));
- return false;
+ 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 true;
+
+ return NULL;
+}
+
+/* Checks that FORMAT is appropriate for a variable of the given WIDTH and
+ returns true if so, otherwise false. */
+bool
+fmt_check_width_compat (struct fmt_spec format, int width)
+{
+ return error_to_bool (fmt_check_width_compat__ (format, NULL, width));
}
/* 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 *format)
+fmt_var_width (struct fmt_spec format)
{
- return (format->type == FMT_AHEX ? format->w / 2
- : format->type == FMT_A ? format->w
+ return (format.type == FMT_AHEX ? format.w / 2
+ : format.type == FMT_A ? format.w
: 0);
}
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])
+fmt_to_string (struct fmt_spec f, char buffer[FMT_STRING_LEN_MAX + 1])
{
- if (fmt_takes_decimals (f->type) || f->d > 0)
+ 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);
+ "%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);
+ "%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)
+fmt_equal (struct fmt_spec a, struct fmt_spec b)
{
- return a->type == b->type && a->w == b->w && a->d == b->d;
+ 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.
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;
}
}
+/* Translate U32, which is in the form found in SAV and SPV files, into a
+ format specification, and stores the new specification in *F.
+
+ 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.
+
+ 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;
+
+ 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 fmt_check_width_compat (*f, width);
+}
+
/* Returns true if TYPE may be used as an input format,
false otherwise. */
bool
s2 = "dd-mmm-yyyy HH:MM:SS";
break;
+ 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 = "H:MM";
- s2 = "H:MM:SS";
+ s1 = "HH:MM";
+ s2 = "HH:MM:SS";
break;
case FMT_DTIME:
case FMT_MOYR:
case FMT_WKYR:
case FMT_DATETIME:
+ case FMT_YMDHMS:
+ case FMT_MTIME:
case FMT_TIME:
case FMT_DTIME:
case FMT_WKDAY:
static void
fmt_clamp_decimals (struct fmt_spec *fmt, enum fmt_use use)
{
- int max_d;
-
- max_d = fmt_max_decimals (fmt->type, fmt->w, use);
- if (fmt->d < 0)
- fmt->d = 0;
- else if (fmt->d > max_d)
+ int max_d = fmt_max_decimals (fmt->type, fmt->w, use);
+ if (fmt->d > max_d)
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 = xstrdup_if_nonnull (old->s),
+ .width = old->width,
+ };
}
/* Frees data in AFFIX. */
static void
fmt_affix_free (struct fmt_affix *affix)
{
- if (affix->s[0])
+ if (affix->s)
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)
{
- 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;
+ /* 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
-fmt_number_style_clone (struct fmt_number_style *new,
- const struct fmt_number_style *old)
+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)
{
- 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;
+ 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)
fmt_affix_free (&style->prefix);
fmt_affix_free (&style->suffix);
fmt_affix_free (&style->neg_suffix);
+ free (style);
}
}
return &formats[type];
}
-const struct fmt_spec F_8_0 = {FMT_F, 8, 0};
+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 };