>From 37dba57cc8ec41f4fd84cc6000bc0ee06513f1ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel=20Arruga=20Vivas?= Date: Mon, 21 Dec 2020 18:34:47 +0100 Subject: [PATCH 1/2] format-lisp, format-scheme: Allow omit integer with ngettext. * gettext-tools/src/format-lisp.c (enum format_arg_type): Add FAT_UNDEFINED. (same_elements, is_one_integer_less, compatible_list): New functions. (make_unconstrained_list, shift_list): Use FAT_UNDEFINED instead of FAT_OBJECT. (make_intersected_element): Add FAT_UNDEFINED to the conditions. (make_union_element): Use FAT_UNDEFINED for undefined union types. (format_check): Use compatible_list when equality is false. (print_element): Add printer for FAT_UNDEFINED. * gettext-tools/src/format-scheme.c (enum format_arg_type): Add FAT_UNDEFINED. (same_elements, is_one_integer_less, compatible_list): New functions. (make_unconstrained_list, shift_list): Use FAT_UNDEFINED instead of FAT_OBJECT. (make_intersected_element): Add FAT_UNDEFINED to the conditions. (make_union_element): Use FAT_UNDEFINED for undefined union types. (format_check): Use compatible_list when equality is false. (format_check): Use compatible_list when equality is false. * gettext-tools/tests/format-lisp-2 (f-l-2.data): Add tests for goto format specifier and narrowing types on plural forms. * gettext-tools/tests/format-scheme-2 (f-sc-2.data): Likewise. Co-Authored-By: Bruno Haible --- gettext-tools/src/format-lisp.c | 107 ++++++++++++++++++++++++---- gettext-tools/src/format-scheme.c | 107 ++++++++++++++++++++++++---- gettext-tools/tests/format-lisp-2 | 35 +++++++++ gettext-tools/tests/format-scheme-2 | 35 +++++++++ 4 files changed, 258 insertions(+), 26 deletions(-) diff --git a/gettext-tools/src/format-lisp.c b/gettext-tools/src/format-lisp.c index 94194dbef..fca555215 100644 --- a/gettext-tools/src/format-lisp.c +++ b/gettext-tools/src/format-lisp.c @@ -64,7 +64,8 @@ enum format_arg_type FAT_REAL, /* Meant for objects of type REAL. */ FAT_LIST, /* Meant for proper lists. */ FAT_FORMATSTRING, /* Format strings. */ - FAT_FUNCTION /* Function. */ + FAT_FUNCTION, /* Function. */ + FAT_UNDEFINED /* Undefined parameter (goto holes). */ }; struct format_arg @@ -333,6 +334,89 @@ equal_list (const struct format_arg_list *list1, return true; } +static bool +same_elements (const struct segment * s, unsigned int i, + const struct format_arg * e2) +{ + if (i < s->count) + { + const struct format_arg * e1 = &s->element[i]; + return (e1->repcount == e2->repcount && equal_element (e1, e2)); + } + return false; +} + +static bool +is_one_integer_less (const struct segment * s, unsigned int i, + const struct format_arg * e2) +{ + if (i < s->count) + { + const struct format_arg * e1 = &s->element[i]; + return (e1->repcount == e2->repcount - 1 + && equal_element (e1, e2)); + } + return false; +} + +/* Tests whether the first normalized argument list is equal to the + second one, or it omits at most one numeric or unspecified + argument. */ +/* Memory effects: none. */ +static bool +compatible_list (const struct format_arg_list *list1, + const struct format_arg_list *list2) +{ + unsigned int n, i1, i2; + bool already_skip = false; + + if (equal_list (list1, list2)) + return true; + + n = list2->initial.count; + if (n != list1->initial.count && n - 1 != list1->initial.count) + return false; + for (i1 = 0, i2 = 0; i2 < n; i1++, i2++) + { + const struct format_arg * e2 = &list2->initial.element[i2]; + if (!same_elements (&list1->initial, i1, e2)) + { + if (already_skip || e2->type != FAT_INTEGER) + return false; + + if (!is_one_integer_less (&list1->initial, i1, e2)) + --i1; + + already_skip = true; + } + } + + /* Not all elements have been parsed. */ + if (i1 != list1->initial.count || i2 != list2->initial.count) + return false; + + n = list2->repeated.count; + if (n != list2->repeated.count + && !(n - 1 == list1->initial.count && !already_skip)) + return false; + for (i1 = 0, i2 = 0; i2 < n; i1++, i2++) + { + const struct format_arg * e2 = &list2->repeated.element[i2]; + if (!same_elements (&list1->repeated, i1, e2)) + { + if (already_skip || e2->type != FAT_INTEGER) + return false; + + if (!is_one_integer_less (&list1->repeated, i1, e2)) + --i1; + + already_skip = true; + + } + } + + return (i1 == list1->repeated.count && i2 == list2->repeated.count); +} /* ===================== Incremental memory allocation ===================== */ @@ -605,7 +689,7 @@ make_unconstrained_list () list->repeated.element = XNMALLOC (1, struct format_arg); list->repeated.element[0].repcount = 1; list->repeated.element[0].presence = FCT_OPTIONAL; - list->repeated.element[0].type = FAT_OBJECT; + list->repeated.element[0].type = FAT_UNDEFINED; list->repeated.length = 1; VERIFY_LIST (list); @@ -923,7 +1007,7 @@ shift_list (struct format_arg_list *list, unsigned int n) list->initial.element[i] = list->initial.element[i-1]; list->initial.element[0].repcount = n; list->initial.element[0].presence = FCT_REQUIRED; - list->initial.element[0].type = FAT_OBJECT; + list->initial.element[0].type = FAT_UNDEFINED; list->initial.count++; list->initial.length += n; @@ -952,13 +1036,13 @@ make_intersected_element (struct format_arg *re, re->presence = FCT_OPTIONAL; /* Intersect the arg types. */ - if (e1->type == FAT_OBJECT) + if (e1->type == FAT_OBJECT || e1->type == FAT_UNDEFINED) { re->type = e2->type; if (re->type == FAT_LIST) re->list = copy_list (e2->list); } - else if (e2->type == FAT_OBJECT) + else if (e2->type == FAT_OBJECT || e2->type == FAT_UNDEFINED) { re->type = e1->type; if (re->type == FAT_LIST) @@ -1467,7 +1551,7 @@ make_union_element (struct format_arg *re, else { /* Other union types are too hard to describe precisely. */ - re->type = FAT_OBJECT; + re->type = FAT_UNDEFINED; } } @@ -3481,13 +3565,7 @@ format_check (void *msgid_descr, void *msgstr_descr, bool equality, } else { - struct format_arg_list *intersection = - make_intersected_list (copy_list (spec1->list), - copy_list (spec2->list)); - - if (!(intersection != NULL - && (normalize_list (intersection), - equal_list (intersection, spec2->list)))) + if (!compatible_list (spec2->list, spec1->list)) { if (error_logger) error_logger (_("format specifications in '%s' are not a subset of those in '%s'"), @@ -3539,6 +3617,9 @@ print_element (struct format_arg *element) switch (element->type) { + case FAT_UNDEFINED: + printf ("?"); + break; case FAT_OBJECT: printf ("*"); break; diff --git a/gettext-tools/src/format-scheme.c b/gettext-tools/src/format-scheme.c index 9958a475e..ff106fefe 100644 --- a/gettext-tools/src/format-scheme.c +++ b/gettext-tools/src/format-scheme.c @@ -66,7 +66,8 @@ enum format_arg_type FAT_REAL, /* Meant for objects of type REAL. */ FAT_COMPLEX, /* Meant for objects of type COMPLEX. */ FAT_LIST, /* Meant for proper lists. */ - FAT_FORMATSTRING /* Format strings. */ + FAT_FORMATSTRING, /* Format strings. */ + FAT_UNDEFINED, /* Undefined parameter (goto holes). */ }; struct format_arg @@ -335,6 +336,89 @@ equal_list (const struct format_arg_list *list1, return true; } +static bool +same_elements (const struct segment * s, unsigned int i, + const struct format_arg * e2) +{ + if (i < s->count) + { + const struct format_arg * e1 = &s->element[i]; + return (e1->repcount == e2->repcount && equal_element (e1, e2)); + } + return false; +} + +static bool +is_one_integer_less (const struct segment * s, unsigned int i, + const struct format_arg * e2) +{ + if (i < s->count) + { + const struct format_arg * e1 = &s->element[i]; + return (e1->repcount == e2->repcount - 1 + && equal_element (e1, e2)); + } + return false; +} + +/* Tests whether the first normalized argument list is equal to the + second one, or it omits at most one numeric or unspecified + argument. */ +/* Memory effects: none. */ +static bool +compatible_list (const struct format_arg_list *list1, + const struct format_arg_list *list2) +{ + unsigned int n, i1, i2; + bool already_skip = false; + + if (equal_list (list1, list2)) + return true; + + n = list2->initial.count; + if (n != list1->initial.count && n - 1 != list1->initial.count) + return false; + for (i1 = 0, i2 = 0; i2 < n; i1++, i2++) + { + const struct format_arg * e2 = &list2->initial.element[i2]; + if (!same_elements (&list1->initial, i1, e2)) + { + if (already_skip || e2->type != FAT_INTEGER) + return false; + + if (!is_one_integer_less (&list1->initial, i1, e2)) + --i1; + + already_skip = true; + } + } + + /* Not all elements have been parsed. */ + if (i1 != list1->initial.count || i2 != list2->initial.count) + return false; + + n = list2->repeated.count; + if (n != list2->repeated.count + && !(n - 1 == list1->initial.count && !already_skip)) + return false; + for (i1 = 0, i2 = 0; i2 < n; i1++, i2++) + { + const struct format_arg * e2 = &list2->repeated.element[i2]; + if (!same_elements (&list1->repeated, i1, e2)) + { + if (already_skip || e2->type != FAT_INTEGER) + return false; + + if (!is_one_integer_less (&list1->repeated, i1, e2)) + --i1; + + already_skip = true; + + } + } + + return (i1 == list1->repeated.count && i2 == list2->repeated.count); +} /* ===================== Incremental memory allocation ===================== */ @@ -607,7 +691,7 @@ make_unconstrained_list () list->repeated.element = XNMALLOC (1, struct format_arg); list->repeated.element[0].repcount = 1; list->repeated.element[0].presence = FCT_OPTIONAL; - list->repeated.element[0].type = FAT_OBJECT; + list->repeated.element[0].type = FAT_UNDEFINED; list->repeated.length = 1; VERIFY_LIST (list); @@ -925,7 +1009,7 @@ shift_list (struct format_arg_list *list, unsigned int n) list->initial.element[i] = list->initial.element[i-1]; list->initial.element[0].repcount = n; list->initial.element[0].presence = FCT_REQUIRED; - list->initial.element[0].type = FAT_OBJECT; + list->initial.element[0].type = FAT_UNDEFINED; list->initial.count++; list->initial.length += n; @@ -954,13 +1038,13 @@ make_intersected_element (struct format_arg *re, re->presence = FCT_OPTIONAL; /* Intersect the arg types. */ - if (e1->type == FAT_OBJECT) + if (e1->type == FAT_OBJECT || e1->type == FAT_UNDEFINED) { re->type = e2->type; if (re->type == FAT_LIST) re->list = copy_list (e2->list); } - else if (e2->type == FAT_OBJECT) + else if (e2->type == FAT_OBJECT || e2->type == FAT_UNDEFINED) { re->type = e1->type; if (re->type == FAT_LIST) @@ -1489,7 +1573,7 @@ make_union_element (struct format_arg *re, else { /* Other union types are too hard to describe precisely. */ - re->type = FAT_OBJECT; + re->type = FAT_UNDEFINED; } } @@ -3403,13 +3487,7 @@ format_check (void *msgid_descr, void *msgstr_descr, bool equality, } else { - struct format_arg_list *intersection = - make_intersected_list (copy_list (spec1->list), - copy_list (spec2->list)); - - if (!(intersection != NULL - && (normalize_list (intersection), - equal_list (intersection, spec2->list)))) + if (!compatible_list (spec2->list, spec1->list)) { if (error_logger) error_logger (_("format specifications in '%s' are not a subset of those in '%s'"), @@ -3461,6 +3539,9 @@ print_element (struct format_arg *element) switch (element->type) { + case FAT_UNDEFINED: + printf ("?"); + break; case FAT_OBJECT: printf ("*"); break; diff --git a/gettext-tools/tests/format-lisp-2 b/gettext-tools/tests/format-lisp-2 index a3958e989..0a7d4b3cb 100755 --- a/gettext-tools/tests/format-lisp-2 +++ b/gettext-tools/tests/format-lisp-2 @@ -168,6 +168,41 @@ msgid "abc~{~c~c~}" msgid_plural "abc~{~c~c~}" msgstr[0] "xyz~{~c~d~}" msgstr[1] "xyz~{~c~d~}" +# Valid: goto format specifier on plural forms same +msgid "~*one abc" +msgid_plural "~d abcs" +msgstr[0] "~*one abc" +msgstr[1] "~d abcs" +# Valid: goto format specifier on plural forms different +msgid "~d abc" +msgid_plural "~d abcs" +msgstr[0] "~* xyz" +msgstr[1] "~d xyz" +# Invalid: narrowing type on the translation +msgid "~a thing" +msgid_plural "~a things" +msgstr[0] "~d thing" +msgstr[1] "~d things" +# Invalid: widening type on the translation +msgid "~d thing" +msgid_plural "~d things" +msgstr[0] "~a thing" +msgstr[1] "~a things" +# Invalid: omitting an object-typed variable +msgid "~a's ~d thing" +msgid_plural "~a's ~d things" +msgstr[0] "the ~d thing" +msgstr[1] "the ~a's ~d things" +# Invalid: omitting an object-typed variable +msgid "~s ~d thing" +msgid_plural "~s ~d things" +msgstr[0] "the ~d thing" +msgstr[1] "the ~s ~d things" +# Invalid: jumping an object-typed variable +msgid "~a's ~d thing" +msgid_plural "~a's ~d things" +msgstr[0] "the ~*~d thing" +msgstr[1] "the ~a's ~d things" EOF : ${MSGFMT=msgfmt} diff --git a/gettext-tools/tests/format-scheme-2 b/gettext-tools/tests/format-scheme-2 index 5dd074511..1bd8cbbdd 100755 --- a/gettext-tools/tests/format-scheme-2 +++ b/gettext-tools/tests/format-scheme-2 @@ -183,6 +183,41 @@ msgid "abc~{~c~c~}" msgid_plural "abc~{~c~c~}" msgstr[0] "xyz~{~c~d~}" msgstr[1] "xyz~{~c~d~}" +# Valid: goto format specifier on plural forms same +msgid "~*one abc" +msgid_plural "~d abcs" +msgstr[0] "~*one abc" +msgstr[1] "~d abcs" +# Valid: goto format specifier on plural forms different +msgid "~d abc" +msgid_plural "~d abcs" +msgstr[0] "~* xyz" +msgstr[1] "~d xyz" +# Invalid: narrowing type on the translation +msgid "~a thing" +msgid_plural "~a things" +msgstr[0] "~d thing" +msgstr[1] "~d things" +# Invalid: widening type on the translation +msgid "~d thing" +msgid_plural "~d things" +msgstr[0] "~a thing" +msgstr[1] "~a things" +# Invalid: omitting an object-typed variable +msgid "~a's ~d thing" +msgid_plural "~a's ~d things" +msgstr[0] "the ~d thing" +msgstr[1] "the ~a's ~d things" +# Invalid: omitting an object-typed variable +msgid "~s ~d thing" +msgid_plural "~s ~d things" +msgstr[0] "the ~d thing" +msgstr[1] "the ~s ~d things" +# Invalid: jumping an object-typed variable +msgid "~a's ~d thing" +msgid_plural "~a's ~d things" +msgstr[0] "the ~*~d thing" +msgstr[1] "the ~a's ~d things" EOF : ${MSGFMT=msgfmt} -- 2.29.2