A format string directive here consists of
- an opening brace '{',
- - an optional non-empty sequence of digits,
+ - an optional non-empty sequence of digits
+ or an optional identifier_or_keyword according to
+ <https://doc.rust-lang.org/1.84.0/reference/identifiers.html>
+ (that is, a XID_Start character and a sequence of XID_Continue characters
+ or an underscore '_' and a non-empty sequence of XID_Continue characters),
- optionally, a ':' and a format specifier, where a format specifier is
of the form [[fill]align][sign][#][0][minimumwidth][.precision][type]
where
Numbered ('{m}') and unnumbered ('{}') argument specifications cannot be used
in the same string; that's unsupported (although it does not always lead to
- an error at runtime, see <https://github.com/clitic/formatx/issues/7>). */
+ an error at runtime, see <https://github.com/clitic/formatx/issues/7>).
+
+ Named ('{name}') and unnamed ('{m}', '{}') argument specifications can be
+ used in the same string. In the formatx! arguments, all unnamed arguments
+ must come before all named arguments; but this is not a restriction for the
+ format string.
+
+ In the 'format!' built-in, all arguments that are passed must be used by the
+ format string, but this is not a requirement for formatx!:
+ formatx!("{1} {1}", 44, 55)
+ formatx!("{}", 9, a = 47)
+ are valid (see <https://github.com/clitic/formatx/issues/8>). */
+
+struct named_arg
+{
+ char *name;
+};
struct numbered_arg
{
struct spec
{
unsigned int directives;
+ unsigned int named_arg_count;
unsigned int numbered_arg_count;
+ struct named_arg *named;
struct numbered_arg *numbered;
};
the token, and true is returned. Otherwise, FORMATP will be
unchanged and false is returned. */
+static int
+named_arg_compare (const void *p1, const void *p2)
+{
+ return strcmp (((const struct named_arg *) p1)->name,
+ ((const struct named_arg *) p2)->name);
+}
+
static int
numbered_arg_compare (const void *p1, const void *p2)
{
char **invalid_reason)
{
struct spec spec;
+ unsigned int named_allocated;
unsigned int numbered_allocated;
bool seen_numbered_args;
unsigned int unnumbered_arg_count;
struct spec *result;
spec.directives = 0;
+ spec.named_arg_count = 0;
spec.numbered_arg_count = 0;
+ spec.named = NULL;
spec.numbered = NULL;
+ named_allocated = 0;
numbered_allocated = 0;
seen_numbered_args = false;
unnumbered_arg_count = 0;
else
{
const char *const format_start = format;
+ bool seen_identifier_or_keyword = false;
unsigned int arg_id;
if (c_isdigit (*format))
}
else
{
- /* Numbered and unnumbered specifications are exclusive. */
- if (seen_numbered_args > 0)
+ /* Try to parse an identifier_or_keyword (that is,
+ - a XID_Start character and a sequence of XID_Continue
+ characters
+ - or an underscore '_' and a non-empty sequence of
+ XID_Continue characters). */
+ {
+ ucs4_t uc1;
+ int n1 = u8_mbtouc (&uc1,
+ (const uint8_t *) format,
+ strnlen (format, 4));
+ if (n1 > 0
+ && (uc_is_property_xid_start (uc1) || uc1 == '_'))
+ {
+ const char *name_start = format;
+ const char *f = format + n1;
+
+ for (;;)
+ {
+ ucs4_t uc;
+ int n = u8_mbtouc (&uc,
+ (const uint8_t *) f,
+ strnlen (f, 4));
+ if (n > 0 && uc_is_property_xid_continue (uc))
+ f += n;
+ else
+ break;
+ }
+ if (uc1 != '_' || f > format + 1)
+ {
+ const char *name_end = f;
+ size_t n = name_end - name_start;
+ char *name = XNMALLOC (n + 1, char);
+ memcpy (name, name_start, n);
+ name[n] = '\0';
+
+ if (named_allocated == spec.named_arg_count)
+ {
+ named_allocated = 2 * named_allocated + 1;
+ spec.named = (struct named_arg *) xrealloc (spec.named, named_allocated * sizeof (struct named_arg));
+ }
+ spec.named[spec.named_arg_count].name = name;
+ spec.named_arg_count++;
+
+ format = f;
+ seen_identifier_or_keyword = true;
+ }
+ }
+ }
+ if (!seen_identifier_or_keyword)
{
- *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED ();
- FDI_SET (format - 1, FMTDIR_ERROR);
- goto bad_format;
- }
+ /* Numbered and unnumbered specifications are exclusive. */
+ if (seen_numbered_args > 0)
+ {
+ *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED ();
+ FDI_SET (format - 1, FMTDIR_ERROR);
+ goto bad_format;
+ }
- arg_id = unnumbered_arg_count;
- unnumbered_arg_count++;
+ arg_id = unnumbered_arg_count;
+ unnumbered_arg_count++;
+ }
}
c = *format;
spec.directives++;
- if (numbered_allocated == spec.numbered_arg_count)
+ if (!seen_identifier_or_keyword)
{
- numbered_allocated = 2 * numbered_allocated + 1;
- spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, numbered_allocated * sizeof (struct numbered_arg));
+ if (numbered_allocated == spec.numbered_arg_count)
+ {
+ numbered_allocated = 2 * numbered_allocated + 1;
+ spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, numbered_allocated * sizeof (struct numbered_arg));
+ }
+ spec.numbered[spec.numbered_arg_count].number = arg_id;
+ spec.numbered_arg_count++;
}
- spec.numbered[spec.numbered_arg_count].number = arg_id;
- spec.numbered_arg_count++;
FDI_SET (format, FMTDIR_END);
}
spec.numbered_arg_count = j;
}
+ /* Sort the named argument array, and eliminate duplicates. */
+ if (spec.named_arg_count > 1)
+ {
+ unsigned int i, j;
+
+ qsort (spec.named, spec.named_arg_count, sizeof (struct named_arg),
+ named_arg_compare);
+
+ /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */
+ for (i = j = 0; i < spec.named_arg_count; i++)
+ if (j > 0 && strcmp (spec.named[i].name, spec.named[j-1].name) == 0)
+ free (spec.named[i].name);
+ else
+ {
+ if (j < i)
+ spec.named[j].name = spec.named[i].name;
+ j++;
+ }
+ spec.named_arg_count = j;
+ }
+
result = XMALLOC (struct spec);
*result = spec;
return result;
bad_format:
+ if (spec.named != NULL)
+ {
+ unsigned int i;
+ for (i = 0; i < spec.named_arg_count; i++)
+ free (spec.named[i].name);
+ free (spec.named);
+ }
if (spec.numbered != NULL)
free (spec.numbered);
return NULL;
{
struct spec *spec = (struct spec *) descr;
+ if (spec->named != NULL)
+ {
+ unsigned int i;
+ for (i = 0; i < spec->named_arg_count; i++)
+ free (spec->named[i].name);
+ free (spec->named);
+ }
free (spec->numbered);
free (spec);
}
struct spec *spec2 = (struct spec *) msgstr_descr;
bool err = false;
+ if (spec1->named_arg_count + spec2->named_arg_count > 0)
+ {
+ unsigned int i, j;
+ unsigned int n1 = spec1->named_arg_count;
+ unsigned int n2 = spec2->named_arg_count;
+
+ /* Check the argument names in spec2 are contained in those of spec1.
+ Both arrays are sorted. We search for the first difference. */
+ for (i = 0, j = 0; i < n1 || j < n2; )
+ {
+ int cmp = (i >= n1 ? 1 :
+ j >= n2 ? -1 :
+ strcmp (spec1->named[i].name, spec2->named[j].name));
+
+ if (cmp > 0)
+ {
+ if (error_logger)
+ error_logger (error_logger_data,
+ _("a format specification for argument '%s', as in '%s', doesn't exist in '%s'"),
+ spec2->named[j].name, pretty_msgstr,
+ pretty_msgid);
+ err = true;
+ break;
+ }
+ else if (cmp < 0)
+ {
+ if (equality)
+ {
+ if (error_logger)
+ error_logger (error_logger_data,
+ _("a format specification for argument '%s' doesn't exist in '%s'"),
+ spec1->named[i].name, pretty_msgstr);
+ err = true;
+ break;
+ }
+ else
+ i++;
+ }
+ else
+ j++, i++;
+ }
+ }
+
if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0)
{
unsigned int i, j;
{
struct spec *spec = (struct spec *) descr;
unsigned int last;
- unsigned int i;
+ unsigned int i, j;
if (spec == NULL)
{
printf ("*");
last = number + 1;
}
+ for (j = 0; j < spec->named_arg_count; j++)
+ {
+ if (i > 0 || j > 0)
+ printf (" ");
+ printf ("%s", spec->named[j].name);
+ }
printf (")");
}