From 9b9ebf8f96dd3b142e4202ca4a60feac9db0820e Mon Sep 17 00:00:00 2001 From: Daiki Ueno Date: Mon, 8 Feb 2016 12:30:03 +0900 Subject: javascript: Support '%m$' in format strings Gjs has had support for numbered arguments in format strings since 1.40. Recognize it as well in format-javascript, so msgfmt -c doesn't fail when numbered arguments are in msgstr. Reported by Sean Burke in: https://lists.gnu.org/archive/html/bug-gettext/2015-10/msg00002.html * gettext-tools/src/format-javascript.c (struct numbered_arg): New struct. (struct spec): Rename format_args_count to numbered_arg_count. Add NUMBERED field in place of FORMAT_ARGS. All callers changed. (numbered_arg_compare): New function. (format_parse): Handle numbered arguments. Based on format-awk.c. (format_check): Add check for numbered arguments. * gettext-tools/tests/format-javascript-1: Add tests for numbered arguments. * gettext-tools/tests/lang-javascript: Use numbered argument in format strings. Add check for Gjs version >= 1.40. --- gettext-tools/src/format-javascript.c | 246 +++++++++++++++++++++++++++----- gettext-tools/tests/format-javascript-1 | 12 ++ gettext-tools/tests/lang-javascript | 10 +- 3 files changed, 225 insertions(+), 43 deletions(-) diff --git a/gettext-tools/src/format-javascript.c b/gettext-tools/src/format-javascript.c index c8602c0..9c5ee00 100644 --- a/gettext-tools/src/format-javascript.c +++ b/gettext-tools/src/format-javascript.c @@ -34,15 +34,15 @@ #define _(str) gettext (str) -/* Although JavaScript specification itself does not define any format - strings, many implementations provide printf-like functions. - We provide a permissive parser which accepts commonly used format - strings, where: +/* JavaScript format strings are not in the language specification, + but there are several implementations which provide the printf-like + feature. Here, we provide a permissive parser which at least accepts + format strings supported by Gjs version 1.40, where: A directive - - starts with '%', + - starts with '%' or '%m$' where m is a positive integer, - is optionally followed by any of the characters '0', '-', ' ', - or, each of which acts as a flag, + or 'I', each of which acts as a flag, - is optionally followed by a width specification: a nonempty digit sequence, - is optionally followed by '.' and a precision specification: a nonempty @@ -65,12 +65,18 @@ enum format_arg_type FAT_FLOAT }; +struct numbered_arg +{ + unsigned int number; + enum format_arg_type type; +}; + struct spec { unsigned int directives; - unsigned int format_args_count; + unsigned int numbered_arg_count; unsigned int allocated; - enum format_arg_type *format_args; + struct numbered_arg *numbered; }; /* Locale independent test for a decimal digit. @@ -80,32 +86,71 @@ struct spec #define isdigit(c) ((unsigned int) ((c) - '0') < 10) +static int +numbered_arg_compare (const void *p1, const void *p2) +{ + unsigned int n1 = ((const struct numbered_arg *) p1)->number; + unsigned int n2 = ((const struct numbered_arg *) p2)->number; + + return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0); +} + static void * format_parse (const char *format, bool translated, char *fdi, char **invalid_reason) { const char *const format_start = format; struct spec spec; + unsigned int unnumbered_arg_count; struct spec *result; spec.directives = 0; - spec.format_args_count = 0; + spec.numbered_arg_count = 0; spec.allocated = 0; - spec.format_args = NULL; + spec.numbered = NULL; + unnumbered_arg_count = 0; for (; *format != '\0';) if (*format++ == '%') { /* A directive. */ + unsigned int number = 0; enum format_arg_type type; FDI_SET (format - 1, FMTDIR_START); spec.directives++; + if (isdigit (*format)) + { + const char *f = format; + unsigned int m = 0; + + do + { + m = 10 * m + (*f - '0'); + f++; + } + while (isdigit (*f)); + + if (*f == '$') + { + if (m == 0) + { + *invalid_reason = INVALID_ARGNO_0 (spec.directives); + FDI_SET (f, FMTDIR_ERROR); + goto bad_format; + } + number = m; + format = ++f; + } + } + + /* Parse flags. */ while (*format == '-' || *format == '+' || *format == ' ' || *format == '0' || *format == 'I') format++; + /* Parse width. */ while (isdigit (*format)) format++; @@ -152,15 +197,50 @@ format_parse (const char *format, bool translated, char *fdi, goto bad_format; } - if (*format != '%') + if (type != FAT_NONE) { - if (spec.allocated == spec.format_args_count) + if (number) + { + /* Numbered argument. */ + + /* Numbered and unnumbered specifications are exclusive. */ + if (unnumbered_arg_count > 0) + { + *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == spec.numbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[spec.numbered_arg_count].number = number; + spec.numbered[spec.numbered_arg_count].type = type; + spec.numbered_arg_count++; + } + else { - spec.allocated = 2 * spec.allocated + 1; - spec.format_args = (enum format_arg_type *) xrealloc (spec.format_args, spec.allocated * sizeof (enum format_arg_type)); + /* Unnumbered argument. */ + + /* Numbered and unnumbered specifications are exclusive. */ + if (spec.numbered_arg_count > 0) + { + *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == spec.numbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[unnumbered_arg_count].number = unnumbered_arg_count + 1; + spec.numbered[unnumbered_arg_count].type = type; + unnumbered_arg_count++; } - spec.format_args[spec.format_args_count] = type; - spec.format_args_count++; } FDI_SET (format, FMTDIR_END); @@ -168,13 +248,63 @@ format_parse (const char *format, bool translated, char *fdi, format++; } + /* Convert the unnumbered argument array to numbered arguments. */ + if (unnumbered_arg_count > 0) + spec.numbered_arg_count = unnumbered_arg_count; + /* Sort the numbered argument array, and eliminate duplicates. */ + else if (spec.numbered_arg_count > 1) + { + unsigned int i, j; + bool err; + + qsort (spec.numbered, spec.numbered_arg_count, + sizeof (struct numbered_arg), numbered_arg_compare); + + /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */ + err = false; + for (i = j = 0; i < spec.numbered_arg_count; i++) + if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number) + { + enum format_arg_type type1 = spec.numbered[i].type; + enum format_arg_type type2 = spec.numbered[j-1].type; + enum format_arg_type type_both; + + if (type1 == type2) + type_both = type1; + else + { + /* Incompatible types. */ + type_both = FAT_NONE; + if (!err) + *invalid_reason = + INVALID_INCOMPATIBLE_ARG_TYPES (spec.numbered[i].number); + err = true; + } + + spec.numbered[j-1].type = type_both; + } + else + { + if (j < i) + { + spec.numbered[j].number = spec.numbered[i].number; + spec.numbered[j].type = spec.numbered[i].type; + } + j++; + } + spec.numbered_arg_count = j; + if (err) + /* *invalid_reason has already been set above. */ + goto bad_format; + } + result = XMALLOC (struct spec); *result = spec; return result; bad_format: - if (spec.format_args != NULL) - free (spec.format_args); + if (spec.numbered != NULL) + free (spec.numbered); return NULL; } @@ -183,8 +313,8 @@ format_free (void *descr) { struct spec *spec = (struct spec *) descr; - if (spec->format_args != NULL) - free (spec->format_args); + if (spec->numbered != NULL) + free (spec->numbered); free (spec); } @@ -205,30 +335,68 @@ format_check (void *msgid_descr, void *msgstr_descr, bool equality, struct spec *spec2 = (struct spec *) msgstr_descr; bool err = false; - if (spec1->format_args_count + spec2->format_args_count > 0) + if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0) { - unsigned int i; + unsigned int i, j; + unsigned int n1 = spec1->numbered_arg_count; + unsigned int n2 = spec2->numbered_arg_count; - /* Check the argument types are the same. */ - if (spec1->format_args_count != spec2->format_args_count) + /* Check the argument names are the same. + Both arrays are sorted. We search for the first difference. */ + for (i = 0; i < spec2->numbered_arg_count; i++) { - if (error_logger) - error_logger (_("number of format specifications in '%s' and '%s' does not match"), - pretty_msgid, pretty_msgstr); - err = true; - } - else - for (i = 0; i < spec2->format_args_count; i++) - if (!(spec1->format_args[i] == spec2->format_args[i] - || (!equality - && (spec1->format_args[i] == FAT_ANY - || spec2->format_args[i] == FAT_ANY)))) + int cmp = (i >= n1 ? 1 : + j >= n2 ? -1 : + spec1->numbered[i].number > spec2->numbered[j].number ? 1 : + spec1->numbered[i].number < spec2->numbered[j].number ? -1 : + 0); + if (cmp > 0) { if (error_logger) - error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"), - pretty_msgid, pretty_msgstr, i + 1); + error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"), + spec2->numbered[j].number, pretty_msgstr, + pretty_msgid); err = true; + break; } + else if (cmp < 0) + { + if (equality) + { + if (error_logger) + error_logger (_("a format specification for argument %u doesn't exist in '%s'"), + spec1->numbered[i].number, pretty_msgstr); + err = true; + break; + } + else + i++; + } + else + j++, i++; + } + /* Check the argument types are the same. */ + if (!err) + for (i = 0, j = 0; j < n2; ) + { + if (spec1->numbered[i].number == spec2->numbered[j].number) + { + if (!equality + && (spec1->numbered[i].type == FAT_ANY + || spec2->numbered[i].type == FAT_ANY)) + { + if (error_logger) + error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"), + pretty_msgid, pretty_msgstr, + spec2->numbered[j].number); + err = true; + break; + } + j++, i++; + } + else + i++; + } } return err; @@ -265,11 +433,11 @@ format_print (void *descr) } printf ("("); - for (i = 0; i < spec->format_args_count; i++) + for (i = 0; i < spec->numbered_arg_count; i++) { if (i > 0) printf (" "); - switch (spec->format_args[i]) + switch (spec->numbered[i].type) { case FAT_ANY: printf ("*"); diff --git a/gettext-tools/tests/format-javascript-1 b/gettext-tools/tests/format-javascript-1 index d53df29..6e434ea 100755 --- a/gettext-tools/tests/format-javascript-1 +++ b/gettext-tools/tests/format-javascript-1 @@ -42,6 +42,18 @@ cat <<\EOF > f-js-1.data "abc%.4.2f" # Valid: three arguments "abc%d%j%j" +# Valid: a numbered argument +"abc%1$d" +# Invalid: zero +"abc%0$d" +# Valid: two-digit numbered arguments +"abc%11$def%10$dgh%9$dij%8$dkl%7$dmn%6$dop%5$dqr%4$dst%3$duv%2$dwx%1$dyz" +# Invalid: unterminated number +"abc%1" +# Invalid: flags before number +"abc%+1$d" +# Invalid: mixing of numbered and unnumbered arguments +"abc%d%2$x" EOF : ${XGETTEXT=xgettext} diff --git a/gettext-tools/tests/lang-javascript b/gettext-tools/tests/lang-javascript index fd018a6..5f502aa 100755 --- a/gettext-tools/tests/lang-javascript +++ b/gettext-tools/tests/lang-javascript @@ -44,7 +44,7 @@ msgstr " # Reverse the arguments. #, javascript-format msgid "%s is replaced by %s." -msgstr "%s remplace %s." +msgstr "%2$s remplace %1$s." EOF : ${MSGMERGE=msgmerge} @@ -66,7 +66,9 @@ ${MSGFMT} -o fr/LC_MESSAGES/prog.mo fr.po (gjs -c imports.gettext) >/dev/null 2>/dev/null \ || { echo "Skipping test: gjs gettext module not found"; exit 77; } (gjs -c imports.format) >/dev/null 2>/dev/null \ - || { echo "Skipping test: gjs format module not found"; exit 77; } + || { echo "Skipping test: gjs format module not found"; exit 77; } +(pkg-config gjs-1.0 --atleast-version=1.40) >/dev/null 2>/dev/null \ + || { echo "Skipping test: gjs version is older than 1.40"; exit 77; } # Test which of the fr_FR locales are installed. : ${LOCALE_FR=fr_FR} @@ -99,11 +101,11 @@ fi : ${DIFF=diff} cat <<\EOF > prog.ok «Votre commande, s'il vous plait», dit le garçon. -FF remplace EUR. +EUR remplace FF. EOF cat <<\EOF > prog.oku «Votre commande, s'il vous plait», dit le garçon. -FF remplace EUR. +EUR remplace FF. EOF : ${LOCALE_FR=fr_FR} -- cgit v1.1