]> git.ipfire.org Git - thirdparty/gettext.git/commitdiff
Move the plural_eval function to a separate file.
authorBruno Haible <bruno@clisp.org>
Tue, 23 Oct 2001 09:03:41 +0000 (09:03 +0000)
committerBruno Haible <bruno@clisp.org>
Tue, 23 Oct 2001 09:03:41 +0000 (09:03 +0000)
Add a plural formula check to msgfmt.

17 files changed:
ChangeLog
configure.in
intl/ChangeLog
intl/Makefile.in
intl/dcigettext.c
intl/plural-eval.c [new file with mode: 0644]
intl/plural-exp.h
m4/ChangeLog
m4/Makefile.am
m4/siginfo.m4 [new file with mode: 0644]
src/ChangeLog
src/Makefile.am
src/msgfmt.c
src/plural-eval.c [new file with mode: 0644]
tests/ChangeLog
tests/Makefile.am
tests/msgfmt-6 [new file with mode: 0755]

index 9fee98ab3ed5fb8b8fc4133a318850b4227ef58d..450111deebc22440571d44324e7443b056d4100f 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2001-09-23  Bruno Haible  <haible@clisp.cons.org>
+
+       * configure.in: Call gt_SIGINFO.
+
 2001-10-20  Bruno Haible  <haible@clisp.cons.org>
 
        * DISCLAIM: Update from
index e4af6b2d2ed6e8d31c79f75695d3bfb258f7b245..124a28a17e97803bd69357c538c6438b345dff8a 100644 (file)
@@ -81,6 +81,7 @@ gt_UNION_WAIT
 gt_TMPDIR
 gt_FUNC_MKDTEMP
 gt_SIGNALBLOCKING
+gt_SIGINFO
 gt_FUNC_SETENV
 
 AM_FUNC_ERROR_AT_LINE
index 50f3104c5de0f33dffb26b89447d106684d579d8..8e52af80c0b9770f0dabdeb1026e6fc1f5cd85c0 100644 (file)
@@ -1,3 +1,12 @@
+2001-09-22  Bruno Haible  <haible@clisp.cons.org>
+
+       * 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  <haible@clisp.cons.org>
 
        * plural-exp.c (EXTRACT_PLURAL_EXPRESSION): Reject numbers that don't
index 52e466f5e3250dfef11077952f147f8828a3c0b9..96f7a3a3a718819d4bec83cc268ca2815b159397 100644 (file)
@@ -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
 
index 648d06e6693f5322e44e2cf8617b608d2f8bea9d..f7249cc40ba855ebaf337ed47c3735c56d29e40d 100644 (file)
@@ -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 (file)
index 0000000..e35a6fa
--- /dev/null
@@ -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 <config.h>
+#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;
+}
index 3b668d8b87d204dabbafec35fd3cb99ea1cf4be2..52eec48b6705d7dd0cafa9e89177a6e0fb04a263 100644 (file)
@@ -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 <drepper@cygnus.com>, 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 */
index 5dcdead865d0b189d6e1528f25a8e164faec9496..b2a6ed64141b419160825fd313c942aac7c85c7a 100644 (file)
@@ -1,3 +1,8 @@
+2001-09-23  Bruno Haible  <haible@clisp.cons.org>
+
+       * siginfo.m4: New file.
+       * Makefile.am (EXTRA_DIST): Add it.
+
 2001-09-18  Bruno Haible  <haible@clisp.cons.org>
 
        * gettext.m4 (AM_WITH_NLS): Test for msgmerge which understands the
index 340702aacc140f69579a4a15b827dbda7988e130..41434b5ad34ff9c6fdba96cfe6abd11e3a61f41c 100644 (file)
@@ -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 (file)
index 0000000..e70c536
--- /dev/null
@@ -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 <signal.h>], [
+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 <signal.h> defines the siginfo_t type,
+   and struct sigaction has the sa_sigaction member and the SA_SIGINFO flag.])
+  fi
+])
index 37ce20a079b01c2834055dd15a767236a9e3a636..214f021048187b63a3035fcca5e0ac51408a5e42 100644 (file)
@@ -1,3 +1,13 @@
+2001-09-23  Bruno Haible  <haible@clisp.cons.org>
+
+       * 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  <tommy.johansson@kanalen.org>
 
        * x-java.l (object_list): New type.
index 2a423636b11176e1630d26b84aac282785b12486..3165737d3f2f5f5c5747542b2d655c92f7e5a19a 100644 (file)
@@ -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)
index 8ec790494be2d4f9dfb4d561c3a3eb3c67939b4f..9f4b0b73f1f523e4849eb26725845482c369032d 100644 (file)
 # include <config.h>
 #endif
 
+#include <ctype.h>
 #include <errno.h>
 #include <getopt.h>
 #include <limits.h>
+#include <setjmp.h>
+#include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <locale.h>
@@ -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 (file)
index 0000000..38f0b41
--- /dev/null
@@ -0,0 +1,21 @@
+/* Expression evaluation for plural form selection.
+   Copyright (C) 2000, 2001 Free Software Foundation, Inc.
+   Written by Ulrich Drepper <drepper@cygnus.com>, 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"
index d37b409a5e0bdb238e5bee8beda1e565c4a2efc9..f649d2a624df7b28dd163aadf90201830f77d66c 100644 (file)
@@ -1,3 +1,8 @@
+2001-09-23  Bruno Haible  <haible@clisp.cons.org>
+
+       * msgfmt-6: New file.
+       * Makefile.am (TESTS): Add it.
+
 2001-10-22  Bruno Haible  <haible@clisp.cons.org>
 
        * Makefile.am (TESTS): Add xgettext-15, xgettext-16.
index a14ca588378b7e5ad703c70c6e91910d02253b67..cd2507c76feac9fd5d937858506ade31aa12716f 100644 (file)
@@ -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 (executable)
index 0000000..5c96b5a
--- /dev/null
@@ -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 <<EOF > 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 <haible@clisp.cons.org>\n"
+"Language-Team: test <test@li.org>\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