From: Bruno Haible Date: Tue, 23 Oct 2001 09:03:41 +0000 (+0000) Subject: Move the plural_eval function to a separate file. X-Git-Tag: v0.11~430 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6b1fb4039266b30dc82c7b0ad1c12aefe3471678;p=thirdparty%2Fgettext.git Move the plural_eval function to a separate file. Add a plural formula check to msgfmt. --- diff --git a/ChangeLog b/ChangeLog index 9fee98ab3..450111dee 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2001-09-23 Bruno Haible + + * configure.in: Call gt_SIGINFO. + 2001-10-20 Bruno Haible * DISCLAIM: Update from diff --git a/configure.in b/configure.in index e4af6b2d2..124a28a17 100644 --- a/configure.in +++ b/configure.in @@ -81,6 +81,7 @@ gt_UNION_WAIT gt_TMPDIR gt_FUNC_MKDTEMP gt_SIGNALBLOCKING +gt_SIGINFO gt_FUNC_SETENV AM_FUNC_ERROR_AT_LINE diff --git a/intl/ChangeLog b/intl/ChangeLog index 50f3104c5..8e52af80c 100644 --- a/intl/ChangeLog +++ b/intl/ChangeLog @@ -1,3 +1,12 @@ +2001-09-22 Bruno Haible + + * plural-eval.c: New file, extracted from dcigettext.c. + * plural-exp.h (PLURAL_EVAL): New declaration. + * dcigettext.c (plural_eval): Remove function, moved to plural-eval.c. + (plural_lookup): Call PLURAL_EVAL instead of plural_eval. + * Makefile.in (COMSRCS): Add plural-eval.c. + (OBJECTS): Add plural-eval.$lo. + 2001-09-22 Bruno Haible * plural-exp.c (EXTRACT_PLURAL_EXPRESSION): Reject numbers that don't diff --git a/intl/Makefile.in b/intl/Makefile.in index 52e466f5e..96f7a3a3a 100644 --- a/intl/Makefile.in +++ b/intl/Makefile.in @@ -65,11 +65,11 @@ SOURCES = $(COMSRCS) intl-compat.c COMSRCS = bindtextdom.c dcgettext.c dgettext.c gettext.c \ finddomain.c loadmsgcat.c localealias.c textdomain.c l10nflist.c \ explodename.c dcigettext.c dcngettext.c dngettext.c ngettext.c plural.y \ -plural-exp.c localcharset.c +plural-exp.c plural-eval.c localcharset.c OBJECTS = @INTLOBJS@ bindtextdom.$lo dcgettext.$lo dgettext.$lo gettext.$lo \ finddomain.$lo loadmsgcat.$lo localealias.$lo textdomain.$lo l10nflist.$lo \ explodename.$lo dcigettext.$lo dcngettext.$lo dngettext.$lo ngettext.$lo \ -plural.$lo plural-exp.$lo localcharset.$lo +plural.$lo plural-exp.$lo plural-eval.$lo localcharset.$lo GETTOBJS = intl-compat.$lo DISTFILES.common = Makefile.in \ config.charset locale.alias ref-add.sin ref-del.sin $(HEADERS) $(SOURCES) @@ -257,7 +257,7 @@ $(OBJECTS): ../config.h libgnuintl.h bindtextdom.$lo dcgettext.$lo dcigettext.$lo dcngettext.$lo dgettext.$lo dngettext.$lo finddomain.$lo gettext.$lo intl-compat.$lo loadmsgcat.$lo localealias.$lo ngettext.$lo textdomain.$lo: gettextP.h gettext.h loadinfo.h dcigettext.$lo: hash-string.h explodename.$lo l10nflist.$lo: loadinfo.h -dcigettext.$lo loadmsgcat.$lo plural.$lo plural-exp.$lo: plural-exp.h +dcigettext.$lo loadmsgcat.$lo plural.$lo plural-exp.$lo plural-eval.$lo: plural-exp.h tags: TAGS diff --git a/intl/dcigettext.c b/intl/dcigettext.c index 648d06e66..f7249cc40 100644 --- a/intl/dcigettext.c +++ b/intl/dcigettext.c @@ -287,9 +287,6 @@ static char *plural_lookup PARAMS ((struct loaded_l10nfile *domain, const char *translation, size_t translation_len)) internal_function; -static unsigned long int plural_eval PARAMS ((struct expression *pexp, - unsigned long int n)) - internal_function; static const char *category_to_name PARAMS ((int category)) internal_function; static const char *guess_category_value PARAMS ((int category, const char *categoryname)) @@ -972,7 +969,7 @@ plural_lookup (domain, n, translation, translation_len) unsigned long int index; const char *p; - index = plural_eval (domaindata->plural, n); + index = PLURAL_EVAL (domaindata->plural, n); if (index >= domaindata->nplurals) /* This should never happen. It means the plural expression and the given maximum value do not match. */ @@ -1000,87 +997,6 @@ plural_lookup (domain, n, translation, translation_len) } -/* Function to evaluate the plural expression and return an index value. */ -static unsigned long int -internal_function -plural_eval (pexp, n) - struct expression *pexp; - unsigned long int n; -{ - switch (pexp->nargs) - { - case 0: - switch (pexp->operation) - { - case var: - return n; - case num: - return pexp->val.num; - default: - break; - } - /* NOTREACHED */ - break; - case 1: - { - /* pexp->operation must be lnot. */ - unsigned long int arg = plural_eval (pexp->val.args[0], n); - return ! arg; - } - case 2: - { - unsigned long int leftarg = plural_eval (pexp->val.args[0], n); - if (pexp->operation == lor) - return leftarg || plural_eval (pexp->val.args[1], n); - else if (pexp->operation == land) - return leftarg && plural_eval (pexp->val.args[1], n); - else - { - unsigned long int rightarg = plural_eval (pexp->val.args[1], n); - - switch (pexp->operation) - { - case mult: - return leftarg * rightarg; - case divide: - return leftarg / rightarg; - case module: - return leftarg % rightarg; - case plus: - return leftarg + rightarg; - case minus: - return leftarg - rightarg; - case less_than: - return leftarg < rightarg; - case greater_than: - return leftarg > rightarg; - case less_or_equal: - return leftarg <= rightarg; - case greater_or_equal: - return leftarg >= rightarg; - case equal: - return leftarg == rightarg; - case not_equal: - return leftarg != rightarg; - default: - break; - } - } - /* NOTREACHED */ - break; - } - case 3: - { - /* pexp->operation must be qmop. */ - unsigned long int boolarg = plural_eval (pexp->val.args[0], n); - return plural_eval (pexp->val.args[boolarg ? 1 : 2], n); - } - } - /* NOTREACHED */ - return 0; -} - - /* Return string representation of locale CATEGORY. */ static const char * internal_function diff --git a/intl/plural-eval.c b/intl/plural-eval.c new file mode 100644 index 000000000..e35a6fa72 --- /dev/null +++ b/intl/plural-eval.c @@ -0,0 +1,101 @@ +/* Plural expression evaluation. + Copyright (C) 2000, 2001 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, 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. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "plural-exp.h" + +unsigned long int +internal_function +PLURAL_EVAL (pexp, n) + struct expression *pexp; + unsigned long int n; +{ + switch (pexp->nargs) + { + case 0: + switch (pexp->operation) + { + case var: + return n; + case num: + return pexp->val.num; + default: + break; + } + /* NOTREACHED */ + break; + case 1: + { + /* pexp->operation must be lnot. */ + unsigned long int arg = PLURAL_EVAL (pexp->val.args[0], n); + return ! arg; + } + case 2: + { + unsigned long int leftarg = PLURAL_EVAL (pexp->val.args[0], n); + if (pexp->operation == lor) + return leftarg || PLURAL_EVAL (pexp->val.args[1], n); + else if (pexp->operation == land) + return leftarg && PLURAL_EVAL (pexp->val.args[1], n); + else + { + unsigned long int rightarg = PLURAL_EVAL (pexp->val.args[1], n); + + switch (pexp->operation) + { + case mult: + return leftarg * rightarg; + case divide: + return leftarg / rightarg; + case module: + return leftarg % rightarg; + case plus: + return leftarg + rightarg; + case minus: + return leftarg - rightarg; + case less_than: + return leftarg < rightarg; + case greater_than: + return leftarg > rightarg; + case less_or_equal: + return leftarg <= rightarg; + case greater_or_equal: + return leftarg >= rightarg; + case equal: + return leftarg == rightarg; + case not_equal: + return leftarg != rightarg; + default: + break; + } + } + /* NOTREACHED */ + break; + } + case 3: + { + /* pexp->operation must be qmop. */ + unsigned long int boolarg = PLURAL_EVAL (pexp->val.args[0], n); + return PLURAL_EVAL (pexp->val.args[boolarg ? 1 : 2], n); + } + } + /* NOTREACHED */ + return 0; +} diff --git a/intl/plural-exp.h b/intl/plural-exp.h index 3b668d8b8..52eec48b6 100644 --- a/intl/plural-exp.h +++ b/intl/plural-exp.h @@ -1,4 +1,4 @@ -/* Expression parsing for plural form selection. +/* Expression parsing and evaluation for plural form selection. Copyright (C) 2000, 2001 Free Software Foundation, Inc. Written by Ulrich Drepper , 2000. @@ -92,15 +92,19 @@ struct parse_args # define FREE_EXPRESSION __gettext_free_exp # define PLURAL_PARSE __gettextparse # define EXTRACT_PLURAL_EXPRESSION __gettext_extract_plural +# define PLURAL_EVAL __gettext_plural_eval #elif defined (IN_LIBINTL) # define FREE_EXPRESSION gettext_free_exp__ # define PLURAL_PARSE gettextparse__ # define EXTRACT_PLURAL_EXPRESSION gettext_extract_plural__ +# define PLURAL_EVAL gettext_plural_eval__ #else # define FREE_EXPRESSION free_plural_expression # define PLURAL_PARSE parse_plural_expression # define EXTRACT_PLURAL_EXPRESSION extract_plural_expression +# define PLURAL_EVAL plural_eval #endif + extern void FREE_EXPRESSION PARAMS ((struct expression *exp)) internal_function; extern int PLURAL_PARSE PARAMS ((void *arg)); @@ -109,4 +113,9 @@ extern void EXTRACT_PLURAL_EXPRESSION PARAMS ((const char *nullentry, unsigned long int *npluralsp)) internal_function; +/* Evaluate the plural expression and return an index value. */ +extern unsigned long int PLURAL_EVAL PARAMS ((struct expression *pexp, + unsigned long int n)) + internal_function; + #endif /* _PLURAL_EXP_H */ diff --git a/m4/ChangeLog b/m4/ChangeLog index 5dcdead86..b2a6ed641 100644 --- a/m4/ChangeLog +++ b/m4/ChangeLog @@ -1,3 +1,8 @@ +2001-09-23 Bruno Haible + + * siginfo.m4: New file. + * Makefile.am (EXTRA_DIST): Add it. + 2001-09-18 Bruno Haible * gettext.m4 (AM_WITH_NLS): Test for msgmerge which understands the diff --git a/m4/Makefile.am b/m4/Makefile.am index 340702aac..41434b5ad 100644 --- a/m4/Makefile.am +++ b/m4/Makefile.am @@ -10,5 +10,5 @@ EXTRA_DIST = README \ backupfile.m4 c-bs-a.m4 codeset.m4 flex.m4 getline.m4 gettext.m4 \ glibc21.m4 iconv.m4 inttypes_h.m4 isc-posix.m4 javacomp.m4 javaexec.m4 \ lcmessage.m4 libtool.m4 mbrtowc.m4 mbstate_t.m4 mbswidth.m4 mkdtemp.m4 \ -progtest.m4 setenv.m4 setlocale.m4 signalblocking.m4 signed.m4 ssize_t.m4 \ -stdbool.m4 tmpdir.m4 uintmax_t.m4 ulonglong.m4 unionwait.m4 +progtest.m4 setenv.m4 setlocale.m4 siginfo.m4 signalblocking.m4 signed.m4 \ +ssize_t.m4 stdbool.m4 tmpdir.m4 uintmax_t.m4 ulonglong.m4 unionwait.m4 diff --git a/m4/siginfo.m4 b/m4/siginfo.m4 new file mode 100644 index 000000000..e70c536b1 --- /dev/null +++ b/m4/siginfo.m4 @@ -0,0 +1,46 @@ +#serial 1 + +# Determine how to determine the precise cause of a signal, for example +# division by zero. +# - SUSV2 and POSIX specify the use of sigaction with SA_SIGINFO and a member +# void (*)(int sig, siginfo_t *info, void *context) sa_sigaction. +# Linux (2.2.x and newer) and Solaris implement this. +# Linux (2.4.x and newer) on i386, m68k, sparc, sparc64, ia64 actually +# deliver FPE_INTDIV. +# - Without SA_SIGINFO: +# - Linux on m68k calls the handler as +# void (*)(int sig, int code, struct sigcontext* scp). +# For division by zero, code would be VEC_ZERODIV<<2. +# - Linux on sparc calls the handler either as +# void (*)(int sig, int code, struct sigcontext* scp), +# code for division by zero would be SUBSIG_IDIVZERO, or as +# void (*)(int sig, siginfo_t *info, void *context). +# Which one depends on a process specific flag in the kernel. +# - Linux on sparc64 always calls the handler as +# void (*)(int sig, siginfo_t *info, void *context). +# - FreeBSD on i386 calls the handler as +# void (*)(int sig, int code, void* scp, char* addr). +# For division by zero, code would be FPE_INTDIV. +# - SunOS 4 calls the handler as +# void (*)(int sig, int code, void* scp, char* addr). +# - Solaris? +# - Irix 5, OSF/1, AIX call the handler as +# void (*)(int sig, int code, struct sigcontext *scp). +# These are so many OS and CPU dependencies that we don't bother, and rely +# only on SA_SIGINFO. +AC_DEFUN([gt_SIGINFO], +[ + AC_CACHE_CHECK([for signal handlers with siginfo_t], gt_cv_siginfo_t, + [AC_TRY_COMPILE([ +#include ], [ +struct sigaction action; +siginfo_t info; +action.sa_flags = SA_SIGINFO; +action.sa_sigaction = (void *) 0; +], gt_cv_siginfo_t=yes, gt_cv_siginfo_t=no)]) + if test $gt_cv_siginfo_t = yes; then + AC_DEFINE(HAVE_SIGINFO, 1, + [Define to 1 if defines the siginfo_t type, + and struct sigaction has the sa_sigaction member and the SA_SIGINFO flag.]) + fi +]) diff --git a/src/ChangeLog b/src/ChangeLog index 37ce20a07..214f02104 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,3 +1,13 @@ +2001-09-23 Bruno Haible + + * plural-eval.c: New file. + * msgfmt.c: Include plural-exp.h. + (sigfpe_exit, sigfpe_code): New variables. + (sigfpe_handler, install_sigfpe_handler, uninstall_sigfpe_handler, + check_plural_eval, check_plural): New functions. + (main): Call it if --check-header is enabled. + * Makefile.am (msgfmt_SOURCES): Add plural-eval.c. + 2001-10-04 Tommy Johansson * x-java.l (object_list): New type. diff --git a/src/Makefile.am b/src/Makefile.am index 2a423636b..3165737d3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -66,7 +66,7 @@ format-ycp.c gettext_SOURCES = gettext.c ngettext_SOURCES = ngettext.c msgcmp_SOURCES = msgcmp.c $(COMMON_SOURCES) -msgfmt_SOURCES = msgfmt.c $(COMMON_SOURCES) msgl-ascii.c msgl-iconv.c write-mo.c write-java.c plural.c $(FORMAT_SOURCES) +msgfmt_SOURCES = msgfmt.c $(COMMON_SOURCES) msgl-ascii.c msgl-iconv.c write-mo.c write-java.c plural.c plural-eval.c $(FORMAT_SOURCES) msgmerge_SOURCES = msgmerge.c $(COMMON_SOURCES) msgl-ascii.c write-po.c read-po.c msgl-equal.c msgunfmt_SOURCES = msgunfmt.c $(COMMON_SOURCES) msgl-ascii.c write-po.c read-po.c read-mo.c read-java.c xgettext_SOURCES = xgettext.c $(COMMON_SOURCES) msgl-ascii.c write-po.c file-list.c x-c.c x-po.c x-java.l x-ycp.c x-rst.c $(FORMAT_SOURCES) diff --git a/src/msgfmt.c b/src/msgfmt.c index 8ec790494..9f4b0b73f 100644 --- a/src/msgfmt.c +++ b/src/msgfmt.c @@ -20,9 +20,12 @@ # include #endif +#include #include #include #include +#include +#include #include #include #include @@ -36,6 +39,7 @@ #include "getline.h" #include "format.h" #include "xmalloc.h" +#include "plural-exp.h" #include "strstr.h" #include "system.h" #include "msgfmt.h" @@ -164,6 +168,17 @@ static void usage PARAMS ((int status)) static const char *add_mo_suffix PARAMS ((const char *)); static struct msg_domain *new_domain PARAMS ((const char *name, const char *file_name)); +#if HAVE_SIGINFO +static void sigfpe_handler PARAMS ((int sig, siginfo_t *sip, void *scp)); +#else +static void sigfpe_handler PARAMS ((int sig)); +#endif +static void install_sigfpe_handler PARAMS ((void)); +static void uninstall_sigfpe_handler PARAMS ((void)); +static void check_plural_eval PARAMS ((struct expression *plural_expr, + unsigned long nplurals_value, + const lex_pos_ty *header_pos)); +static void check_plural PARAMS ((message_list_ty *mlp)); static void check_pair PARAMS ((const char *msgid, const lex_pos_ty *msgid_pos, const char *msgid_plural, const char *msgstr, size_t msgstr_len, @@ -364,7 +379,6 @@ warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\ translations must be reported. */ po_lex_pass_comments (true); - /* Now write out all domains. */ /* Process all given .po files. */ while (argc > optind) { @@ -379,6 +393,12 @@ warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\ ++optind; } + /* Check the plural expression is present if needed and has valid syntax. */ + if (check_header) + for (domain = domain_list; domain != NULL; domain = domain->next) + check_plural (domain->mlp); + + /* Now write out all domains. */ for (domain = domain_list; domain != NULL; domain = domain->next) { if (java_mode) @@ -567,6 +587,321 @@ new_domain (name, file_name) } +static sigjmp_buf sigfpe_exit; + +#if HAVE_SIGINFO + +static int sigfpe_code; + +/* Signal handler called in case of arithmetic exception (e.g. division + by zero) during plural_eval. */ +static void +sigfpe_handler (sig, sip, scp) + int sig; + siginfo_t *sip; + void *scp; +{ + sigfpe_code = sip->si_code; + siglongjmp (sigfpe_exit, 1); +} + +#else + +/* Signal handler called in case of arithmetic exception (e.g. division + by zero) during plural_eval. */ +static void +sigfpe_handler (sig) + int sig; +{ + siglongjmp (sigfpe_exit, 1); +} + +#endif + +static void +install_sigfpe_handler () +{ +#if HAVE_SIGINFO + struct sigaction action; + action.sa_sigaction = sigfpe_handler; + action.sa_flags = SA_SIGINFO; + sigemptyset (&action.sa_mask); + sigaction (SIGFPE, &action, (struct sigaction *) NULL); +#else + signal (SIGFPE, sigfpe_handler); +#endif +} + +static void +uninstall_sigfpe_handler () +{ +#if HAVE_SIGINFO + struct sigaction action; + action.sa_handler = SIG_DFL; + action.sa_flags = 0; + sigemptyset (&action.sa_mask); + sigaction (SIGFPE, &action, (struct sigaction *) NULL); +#else + signal (SIGFPE, SIG_DFL); +#endif +} + +/* Check the values returned by plural_eval. */ +static void +check_plural_eval (plural_expr, nplurals_value, header_pos) + struct expression *plural_expr; + unsigned long nplurals_value; + const lex_pos_ty *header_pos; +{ + if (sigsetjmp (sigfpe_exit, 1) == 0) + { + unsigned long n; + + /* Protect against arithmetic exceptions. */ + install_sigfpe_handler (); + + for (n = 0; n <= 1000; n++) + { + unsigned long val = plural_eval (plural_expr, n); + + if ((long) val < 0) + { + /* End of protection against arithmetic exceptions. */ + uninstall_sigfpe_handler (); + + error_with_progname = false; + error_at_line (0, 0, + header_pos->file_name, header_pos->line_number, + _("plural expression can produce negative values")); + error_with_progname = true; + exit_status = EXIT_FAILURE; + return; + } + else if (val >= nplurals_value) + { + /* End of protection against arithmetic exceptions. */ + uninstall_sigfpe_handler (); + + error_with_progname = false; + error_at_line (0, 0, + header_pos->file_name, header_pos->line_number, + _("nplurals = %lu but plural expression can produce values as large as %lu"), + nplurals_value, val); + error_with_progname = true; + exit_status = EXIT_FAILURE; + return; + } + } + + /* End of protection against arithmetic exceptions. */ + uninstall_sigfpe_handler (); + } + else + { + /* Caught an arithmetic exception. */ + const char *msg; + + /* End of protection against arithmetic exceptions. */ + uninstall_sigfpe_handler (); + +#if HAVE_SIGINFO + switch (sigfpe_code) +#endif + { +#if HAVE_SIGINFO +# ifdef FPE_INTDIV + case FPE_INTDIV: + msg = _("plural expression can produce division by zero"); + break; +# endif +# ifdef FPE_INTOVF + case FPE_INTOVF: + msg = _("plural expression can produce integer overflow"); + break; +# endif + default: + msg = _("plural expression can produce arithmetic exceptions, possibly division by zero"); +#endif + } + + error_with_progname = false; + error_at_line (0, 0, header_pos->file_name, header_pos->line_number, msg); + error_with_progname = true; + exit_status = EXIT_FAILURE; + } +} + + +/* Perform plural expression checking. */ +static void +check_plural (mlp) + message_list_ty *mlp; +{ + const lex_pos_ty *has_plural; + unsigned long min_nplurals; + const lex_pos_ty *min_pos; + unsigned long max_nplurals; + const lex_pos_ty *max_pos; + size_t j; + message_ty *header; + + /* Determine whether mlp has plural entries. */ + has_plural = NULL; + min_nplurals = ULONG_MAX; + min_pos = NULL; + max_nplurals = 0; + max_pos = NULL; + for (j = 0; j < mlp->nitems; j++) { + message_ty *mp = mlp->item[j]; + + if (mp->msgid_plural != NULL) { + const char *p; + const char *p_end; + unsigned long n; + + if (has_plural == NULL) + has_plural = &mp->pos; + + n = 0; + for (p = mp->msgstr, p_end = p + mp->msgstr_len; + p < p_end; + p += strlen (p) + 1) + n++; + if (min_nplurals > n) { + min_nplurals = n; + min_pos = &mp->pos; + } + if (max_nplurals > n) { + max_nplurals = n; + min_pos = &mp->pos; + } + } + } + + /* Look at the plural entry for this domain. + Cf, function extract_plural_expression. */ + header = message_list_search (mlp, ""); + if (header != NULL) + { + const char *nullentry; + const char *plural; + const char *nplurals; + + nullentry = header->msgstr; + + plural = strstr (nullentry, "plural="); + nplurals = strstr (nullentry, "nplurals="); + if (plural == NULL && has_plural != NULL) + { + error_with_progname = false; + error_at_line (0, 0, has_plural->file_name, has_plural->line_number, + _("message catalog has plural form translations...")); + --error_message_count; + error_at_line (0, 0, header->pos.file_name, header->pos.line_number, + _("...but header entry lacks a \"plural=EXPRESSION\" attribute")); + error_with_progname = true; + exit_status = EXIT_FAILURE; + } + if (nplurals == NULL && has_plural != NULL) + { + error_with_progname = false; + error_at_line (0, 0, has_plural->file_name, has_plural->line_number, + _("message catalog has plural form translations...")); + --error_message_count; + error_at_line (0, 0, header->pos.file_name, header->pos.line_number, + _("...but header entry lacks a \"nplurals=INTEGER\" attribute")); + error_with_progname = true; + exit_status = EXIT_FAILURE; + } + if (plural != NULL && nplurals != NULL) + { + const char *endp; + unsigned long int nplurals_value; + struct parse_args args; + struct expression *plural_expr; + + /* First check the number. */ + nplurals += 9; + while (*nplurals != '\0' && isspace ((unsigned char) *nplurals)) + ++nplurals; + endp = nplurals; + nplurals_value = 0; + if (*nplurals >= '0' && *nplurals <= '9') + nplurals_value = strtoul (nplurals, (char **) &endp, 10); + if (nplurals == endp) + { + error_with_progname = false; + error_at_line (0, 0, + header->pos.file_name, header->pos.line_number, + _("invalid nplurals value")); + error_with_progname = true; + exit_status = EXIT_FAILURE; + } + + /* Then check the expression. */ + plural += 7; + args.cp = plural; + if (parse_plural_expression (&args) != 0) + { + error_with_progname = false; + error_at_line (0, 0, + header->pos.file_name, header->pos.line_number, + _("invalid plural expression")); + error_with_progname = true; + exit_status = EXIT_FAILURE; + } + plural_expr = args.res; + + /* See whether nplurals and plural fit together. */ + if (exit_status != EXIT_FAILURE) + check_plural_eval (plural_expr, nplurals_value, &header->pos); + + /* Check the number of plurals of the translations. */ + if (exit_status != EXIT_FAILURE) + { + if (min_nplurals < nplurals_value) + { + error_with_progname = false; + error_at_line (0, 0, + header->pos.file_name, header->pos.line_number, + _("nplurals = %lu..."), nplurals_value); + --error_message_count; + error_at_line (0, 0, min_pos->file_name, min_pos->line_number, + _("...but some messages have only %lu plural forms"), + min_nplurals); + error_with_progname = true; + exit_status = EXIT_FAILURE; + } + else if (max_nplurals > nplurals_value) + { + error_with_progname = false; + error_at_line (0, 0, + header->pos.file_name, header->pos.line_number, + _("nplurals = %lu..."), nplurals_value); + --error_message_count; + error_at_line (0, 0, max_pos->file_name, max_pos->line_number, + _("...but some messages have %lu plural forms"), + max_nplurals); + error_with_progname = true; + exit_status = EXIT_FAILURE; + } + /* The only valid case is max_nplurals <= n <= min_nplurals, + which means either has_plural == NULL or + max_nplurals = n = min_nplurals. */ + } + } + } + else if (has_plural != NULL) + { + error_with_progname = false; + error_at_line (0, 0, has_plural->file_name, has_plural->line_number, + _("message catalog has plural form translations, but lacks a header entry with \"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\"")); + error_with_progname = true; + exit_status = EXIT_FAILURE; + } +} + + /* Perform miscellaneous checks on a message. */ static void check_pair (msgid, msgid_pos, msgid_plural, msgstr, msgstr_len, msgstr_pos, diff --git a/src/plural-eval.c b/src/plural-eval.c new file mode 100644 index 000000000..38f0b4161 --- /dev/null +++ b/src/plural-eval.c @@ -0,0 +1,21 @@ +/* Expression evaluation for plural form selection. + Copyright (C) 2000, 2001 Free Software Foundation, Inc. + Written by Ulrich Drepper , 2000. + + 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, 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. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +/* Include the expression evaluation code from libintl, with different function + names. */ +#include "../intl/plural-eval.c" diff --git a/tests/ChangeLog b/tests/ChangeLog index d37b409a5..f649d2a62 100644 --- a/tests/ChangeLog +++ b/tests/ChangeLog @@ -1,3 +1,8 @@ +2001-09-23 Bruno Haible + + * msgfmt-6: New file. + * Makefile.am (TESTS): Add it. + 2001-10-22 Bruno Haible * Makefile.am (TESTS): Add xgettext-15, xgettext-16. diff --git a/tests/Makefile.am b/tests/Makefile.am index a14ca5883..cd2507c76 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -26,7 +26,7 @@ TESTS = gettext-1 gettext-2 \ msgcomm-8 msgcomm-9 msgcomm-10 msgcomm-11 msgcomm-12 msgcomm-13 \ msgcomm-14 msgcomm-15 msgcomm-16 msgcomm-17 msgcomm-18 msgcomm-19 \ msgcomm-20 msgcomm-21 msgcomm-22 msgcomm-23 \ - msgfmt-1 msgfmt-2 msgfmt-3 msgfmt-4 msgfmt-5 \ + msgfmt-1 msgfmt-2 msgfmt-3 msgfmt-4 msgfmt-5 msgfmt-6 \ msgmerge-1 msgmerge-2 msgmerge-3 msgmerge-4 msgmerge-5 msgmerge-6 \ msgmerge-7 msgmerge-8 msgmerge-9 msgmerge-10 msgmerge-11 msgmerge-12 \ msgunfmt-1 \ diff --git a/tests/msgfmt-6 b/tests/msgfmt-6 new file mode 100755 index 000000000..5c96b5a4f --- /dev/null +++ b/tests/msgfmt-6 @@ -0,0 +1,33 @@ +#! /bin/sh + +# Test catching of division by zero in plural expression. + +tmpfiles="" +trap 'rm -fr $tmpfiles' 1 2 3 15 + +tmpfiles="$tmpfiles mf-6.po" +cat < mf-6.po +msgid "" +msgstr "" +"Project-Id-Version: msgfmt test 4\n" +"PO-Revision-Date: 2001-09-23 15:03+0200\n" +"Last-Translator: Bruno Haible \n" +"Language-Team: test \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ASCII\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(1 + (1 / (n - 257)));\n" +EOF + +tmpfiles="$tmpfiles mf-6.mo core *.core" +: ${MSGFMT=msgfmt} +${MSGFMT} --check -o mf-6.mo mf-6.po 2>/dev/null +# Exit code must be 1. +# If the division by zero didn't get unnoticed, it would be 0. +# If it produced a core dump, it would be 136 (= 128 + SIGFPE). +test $? = 1 +result=$? + +rm -fr $tmpfiles + +exit $result