]> git.ipfire.org Git - thirdparty/gettext.git/commitdiff
Add support for Object Pascal format strings.
authorBruno Haible <bruno@clisp.org>
Sat, 20 Oct 2001 12:59:54 +0000 (12:59 +0000)
committerBruno Haible <bruno@clisp.org>
Sat, 20 Oct 2001 12:59:54 +0000 (12:59 +0000)
src/ChangeLog
src/Makefile.am
src/format-pascal.c [new file with mode: 0644]
src/format.c
src/format.h
src/message.c
src/message.h
tests/ChangeLog
tests/Makefile.am
tests/format-pascal-1 [new file with mode: 0755]
tests/format-pascal-2 [new file with mode: 0755]

index 9b6f680d494253f495b44470183744ce3d22c949..871082c9d0a929e1d8262f6ab04aff539ee6a496 100644 (file)
@@ -1,3 +1,13 @@
+2001-09-20  Bruno Haible  <haible@clisp.cons.org>
+
+       * format-pascal.c: New file.
+       * format.h (formatstring_pascal): New declaration.
+       * message.h (enum format_type): Add format_pascal.
+       * message.c (format_language): Add format_pascal entry.
+       (format_language_pretty): Likewise.
+       * format.c (formatstring_parsers): Add formatstring_pascal.
+       * Makefile.am (FORMAT_SOURCES): Add format-pascal.c.
+
 2001-09-16  Bruno Haible  <haible@clisp.cons.org>
 
        * x-ycp.h: New file.
index b93a77917da489bc0bc5a36b290153f0b0f82624..73846cd51ffb0f58590a06df701bb58c67766b79 100644 (file)
@@ -59,7 +59,8 @@ open-po.c dir-list.c str-list.c
 
 # xgettext and msgfmt deal with format strings.
 FORMAT_SOURCES = format.c \
-format-c.c format-java.c format-lisp.c format-python.c format-ycp.c
+format-c.c format-java.c format-lisp.c format-python.c format-pascal.c \
+format-ycp.c
 
 # Source dependencies.
 gettext_SOURCES = gettext.c
diff --git a/src/format-pascal.c b/src/format-pascal.c
new file mode 100644 (file)
index 0000000..b7ece57
--- /dev/null
@@ -0,0 +1,530 @@
+/* Object Pascal format strings.
+   Copyright (C) 2001 Free Software Foundation, Inc.
+   Written by Bruno Haible <haible@clisp.cons.org>, 2001.
+
+   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 <stdbool.h>
+#include <stdlib.h>
+
+#include "format.h"
+#include "c-ctype.h"
+#include "xmalloc.h"
+#include "system.h"
+#include "error.h"
+#include "progname.h"
+#include "libgettext.h"
+
+#define _(str) gettext (str)
+
+/* Object Pascal format strings are usable with the "format" function in the
+   "sysutils" unit.  They are implemented in fpc-1.0.4/rtl/objpas/sysstr.inc.
+   Another implementation exists in Borland Delphi.  The GNU Pascal's
+   "sysutils" doesn't (yet?) have the "format" function.
+
+   A directive
+   - starts with '%',
+   - either
+     - is finished with '%', or
+     - - is optionally followed by an index specification: '*' (reads an
+         argument, must be of type integer) or a nonempty digit sequence,
+         followed by ':',
+       - is optionally followed by '-', which acts as a flag,
+       - is optionally followed by a width specification: '*' (reads an
+         argument, must be of type integer) or a nonempty digit sequence,
+       - is optionally followed by '.' and a precision specification: '*'
+         (reads an argument, must be of type integer) or a nonempty digit
+         sequence,
+       - is finished by a case-insensitive specifier. If no index was
+         specified, it reads an argument; otherwise is uses the index-th
+         argument, 0-based.
+         - 'd', needs an 'integer' or 'int64' argument,
+         - 'e', 'f', 'g', 'n', 'm', need an 'extended' floating-point argument,
+         - 's', needs a 'string', 'char', 'pchar' or 'ansistring' argument,
+         - 'p', needs a 'pointer' argument,
+         - 'x', needs an integer argument.
+   Numbered and unnumbered argument specifications can be used in the same
+   string.  Numbered argument specifications have no influence on the
+   "current argument index", that is incremented each time an argument is read.
+ */
+
+enum format_arg_type
+{
+  FAT_INTEGER,         /* integer */
+  FAT_INTEGER64,       /* integer, int64 */
+  FAT_FLOAT,           /* extended */
+  FAT_STRING,          /* string, char, pchar, ansistring */
+  FAT_POINTER
+};
+
+struct numbered_arg
+{
+  unsigned int number;
+  enum format_arg_type type;
+};
+
+struct spec
+{
+  unsigned int directives;
+  unsigned int numbered_arg_count;
+  unsigned int allocated;
+  struct numbered_arg *numbered;
+};
+
+/* Locale independent test for a decimal digit.
+   Argument can be  'char' or 'unsigned char'.  (Whereas the argument of
+   <ctype.h> isdigit must be an 'unsigned char'.)  */
+#undef isdigit
+#define isdigit(c) ((unsigned int) ((c) - '0') < 10)
+
+
+/* Prototypes for local functions.  Needed to ensure compiler checking of
+   function argument counts despite of K&R C function definition syntax.  */
+static int numbered_arg_compare PARAMS ((const void *p1, const void *p2));
+static void *format_parse PARAMS ((const char *format));
+static void format_free PARAMS ((void *descr));
+static int format_get_number_of_directives PARAMS ((void *descr));
+static bool format_check PARAMS ((const lex_pos_ty *pos,
+                                 void *msgid_descr, void *msgstr_descr));
+
+
+static int
+numbered_arg_compare (p1, p2)
+     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 (format)
+     const char *format;
+{
+  unsigned int directives;
+  unsigned int numbered_arg_count;
+  unsigned int allocated;
+  struct numbered_arg *numbered;
+  unsigned int unnumbered_arg_count;
+  struct spec *result;
+
+  enum arg_index
+  {
+    index_numbered,    /* index given by a fixed integer */
+    index_unnumbered,  /* index given by unnumbered_arg_count++ */
+    index_unknown      /* index is only known at run time */
+  };
+
+  directives = 0;
+  numbered_arg_count = 0;
+  allocated = 0;
+  numbered = NULL;
+  unnumbered_arg_count = 0;
+
+  for (; *format != '\0';)
+    if (*format++ == '%')
+      {
+       /* A directive.  */
+       directives++;
+
+       if (*format != '%')
+         {
+           /* A complex directive.  */
+           enum arg_index main_arg = index_unnumbered;
+           unsigned int main_number = 0;
+           enum format_arg_type type;
+
+           if (isdigit (*format))
+             {
+               const char *f = format;
+               unsigned int m = 0;
+
+               do
+                 {
+                   m = 10 * m + (*f - '0');
+                   f++;
+                 }
+               while (isdigit (*f));
+
+               if (*f == ':')
+                 {
+                   main_number = m;
+                   main_arg = index_numbered;
+                   format = ++f;
+                 }
+             }
+           else if (*format == '*')
+             {
+               if (format[1] == ':')
+                 {
+                   main_arg = index_unknown;
+                   format += 2;
+                 }
+             }
+
+           /* Parse flags.  */
+           if (*format == '-')
+             format++;
+
+           /* Parse width.  */
+           if (isdigit (*format))
+             {
+               do
+                 format++;
+               while (isdigit (*format));
+             }
+           else if (*format == '*')
+             {
+               /* Unnumbered argument of type FAT_INTEGER.   */
+               if (allocated == numbered_arg_count)
+                 {
+                   allocated = 2 * allocated + 1;
+                   numbered = (struct numbered_arg *) xrealloc (numbered, allocated * sizeof (struct numbered_arg));
+                 }
+               numbered[numbered_arg_count].number = unnumbered_arg_count;
+               numbered[numbered_arg_count].type = FAT_INTEGER;
+               numbered_arg_count++;
+               unnumbered_arg_count++;
+
+               format++;
+             }
+
+           /* Parse precision.  */
+           if (*format == '.')
+             {
+               format++;
+
+               if (isdigit (*format))
+                 {
+                   do
+                     format++;
+                   while (isdigit (*format));
+                 }
+               else if (*format == '*')
+                 {
+                   /* Unnumbered argument of type FAT_INTEGER.   */
+                   if (allocated == unnumbered_arg_count)
+                     {
+                       allocated = 2 * allocated + 1;
+                       numbered = (struct numbered_arg *) xrealloc (numbered, allocated * sizeof (struct numbered_arg));
+                     }
+                   numbered[numbered_arg_count].number = unnumbered_arg_count;
+                   numbered[numbered_arg_count].type = FAT_INTEGER;
+                   numbered_arg_count++;
+                   unnumbered_arg_count++;
+
+                   format++;
+                 }
+               else
+                 --format;     /* will jump to bad_format */
+             }
+
+           switch (c_tolower (*format))
+             {
+             case 'd':
+               type = FAT_INTEGER64;
+               break;
+             case 'e': case 'f': case 'g': case 'n': case 'm':
+               type = FAT_FLOAT;
+               break;
+             case 's':
+               type = FAT_STRING;
+               break;
+             case 'p':
+               type = FAT_POINTER;
+               break;
+             case 'x':
+               type = FAT_INTEGER;
+               break;
+             default:
+               goto bad_format;
+             }
+
+           if (allocated == numbered_arg_count)
+             {
+               allocated = 2 * allocated + 1;
+               numbered = (struct numbered_arg *) xrealloc (numbered, allocated * sizeof (struct numbered_arg));
+             }
+           switch (main_arg)
+             {
+             case index_unnumbered:
+               numbered[numbered_arg_count].number = unnumbered_arg_count;
+               numbered[numbered_arg_count].type = type;
+               unnumbered_arg_count++;
+               break;
+             case index_numbered:
+               numbered[numbered_arg_count].number = main_number;
+               numbered[numbered_arg_count].type = type;
+               break;
+             case index_unknown:
+               numbered[numbered_arg_count].number = unnumbered_arg_count;
+               numbered[numbered_arg_count].type = FAT_INTEGER;
+               unnumbered_arg_count++;
+               break;
+             default:
+               abort ();
+             }
+           numbered_arg_count++;
+         }
+
+       format++;
+      }
+
+  /* Sort the numbered argument array, and eliminate duplicates.  */
+  if (numbered_arg_count > 1)
+    {
+      unsigned int i, j;
+      bool err;
+
+      qsort (numbered, 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 < numbered_arg_count; i++)
+       if (j > 0 && numbered[i].number == numbered[j-1].number)
+         {
+           enum format_arg_type type1 = numbered[i].type;
+           enum format_arg_type type2 = numbered[j-1].type;
+           enum format_arg_type type_both;
+
+           if (type1 == type2)
+             type_both = type1;
+           else if ((type1 == FAT_INTEGER && type2 == FAT_INTEGER64)
+                    || (type1 == FAT_INTEGER64 && type2 == FAT_INTEGER))
+             type_both = FAT_INTEGER;
+           else
+             /* Incompatible types.  */
+             type_both = type1, err = true;
+
+           numbered[j-1].type = type_both;
+         }
+       else
+         {
+           if (j < i)
+             {
+               numbered[j].number = numbered[i].number;
+               numbered[j].type = numbered[i].type;
+             }
+           j++;
+         }
+      numbered_arg_count = j;
+      if (err)
+       goto bad_format;
+    }
+
+  result = (struct spec *) xmalloc (sizeof (struct spec));
+  result->directives = directives;
+  result->numbered_arg_count = numbered_arg_count;
+  result->allocated = allocated;
+  result->numbered = numbered;
+  return result;
+
+ bad_format:
+  if (numbered != NULL)
+    free (numbered);
+  return NULL;
+}
+
+static void
+format_free (descr)
+     void *descr;
+{
+  struct spec *spec = (struct spec *) descr;
+
+  if (spec->numbered != NULL)
+    free (spec->numbered);
+  free (spec);
+}
+
+static int
+format_get_number_of_directives (descr)
+     void *descr;
+{
+  struct spec *spec = (struct spec *) descr;
+
+  return spec->directives;
+}
+
+static bool
+format_check (pos, msgid_descr, msgstr_descr)
+     const lex_pos_ty *pos;
+     void *msgid_descr;
+     void *msgstr_descr;
+{
+  struct spec *spec1 = (struct spec *) msgid_descr;
+  struct spec *spec2 = (struct spec *) msgstr_descr;
+  bool err = false;
+
+  if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0)
+    {
+      unsigned int i;
+      unsigned int n = MAX (spec1->numbered_arg_count, spec2->numbered_arg_count);
+
+      /* Check the argument names are the same.
+        Both arrays are sorted.  We search for the first difference.  */
+      for (i = 0; i < n; i++)
+       {
+         int cmp = (i >= spec1->numbered_arg_count ? 1 :
+                    i >= spec2->numbered_arg_count ? -1 :
+                    spec1->numbered[i].number > spec2->numbered[i].number ? 1 :
+                    spec1->numbered[i].number < spec2->numbered[i].number ? -1 :
+                    0);
+
+         if (cmp > 0)
+           {
+             error_with_progname = false;
+             error_at_line (0, 0, pos->file_name, pos->line_number,
+                            _("a format specification for argument {%u} doesn't exist in 'msgid'"),
+                            spec2->numbered[i].number);
+             error_with_progname = true;
+             err = true;
+             break;
+           }
+         else if (cmp < 0)
+           {
+             error_with_progname = false;
+             error_at_line (0, 0, pos->file_name, pos->line_number,
+                            _("a format specification for argument {%u} doesn't exist in 'msgstr'"),
+                            spec1->numbered[i].number);
+             error_with_progname = true;
+             err = true;
+             break;
+           }
+       }
+      /* Check the argument types are the same.  */
+      if (!err)
+       for (i = 0; i < spec2->numbered_arg_count; i++)
+         if (spec1->numbered[i].type != spec2->numbered[i].type)
+           {
+             error_with_progname = false;
+             error_at_line (0, 0, pos->file_name, pos->line_number,
+                            _("format specifications in 'msgid' and 'msgstr' for argument {%u} are not the same"),
+                            spec2->numbered[i].number);
+             error_with_progname = true;
+             err = true;
+             break;
+           }
+    }
+
+  return err;
+}
+
+
+struct formatstring_parser formatstring_pascal =
+{
+  format_parse,
+  format_free,
+  format_get_number_of_directives,
+  format_check
+};
+
+
+#ifdef TEST
+
+/* Test program: Print the argument list specification returned by
+   format_parse for strings read from standard input.  */
+
+#include <stdio.h>
+#include "getline.h"
+
+static void
+format_print (descr)
+     void *descr;
+{
+  struct spec *spec = (struct spec *) descr;
+  unsigned int last;
+  unsigned int i;
+
+  if (spec == NULL)
+    {
+      printf ("INVALID");
+      return;
+    }
+
+  printf ("(");
+  last = 0;
+  for (i = 0; i < spec->numbered_arg_count; i++)
+    {
+      unsigned int number = spec->numbered[i].number;
+
+      if (i > 0)
+       printf (" ");
+      if (number < last)
+       abort ();
+      for (; last < number; last++)
+       printf ("_ ");
+      switch (spec->numbered[i].type)
+       {
+       case FAT_INTEGER:
+         printf ("i");
+         break;
+       case FAT_INTEGER64:
+         printf ("I");
+         break;
+       case FAT_FLOAT:
+         printf ("f");
+         break;
+       case FAT_STRING:
+         printf ("s");
+         break;
+       case FAT_POINTER:
+         printf ("p");
+         break;
+       default:
+         abort ();
+       }
+      last = number + 1;
+    }
+  printf (")");
+}
+
+int
+main ()
+{
+  for (;;)
+    {
+      char *line = NULL;
+      size_t line_len = 0;
+      void *descr;
+
+      if (getline (&line, &line_len, stdin) < 0)
+       break;
+
+      descr = format_parse (line);
+
+      format_print (descr);
+      printf ("\n");
+
+      free (line);
+    }
+
+  return 0;
+}
+
+/*
+ * For Emacs M-x compile
+ * Local Variables:
+ * compile-command: "gcc -O -g -Wall -I.. -I../lib -I../intl -DHAVE_CONFIG_H -DTEST format-pascal.c ../lib/libnlsut.a"
+ * End:
+ */
+
+#endif /* TEST */
index 38419d960dffda37d451de6c1d615d5fd2e92642..30b54f8c2f6c633acdf9baeda3426a1147149452 100644 (file)
@@ -30,5 +30,6 @@ struct formatstring_parser *formatstring_parsers[NFORMATS] =
   /* format_python */  &formatstring_python,
   /* format_lisp */    &formatstring_lisp,
   /* format_java */    &formatstring_java,
+  /* format_pascal */  &formatstring_pascal,
   /* format_ycp */     &formatstring_ycp
 };
index 5c6424effbfc67c919fda3ef6e04c6455b703225..9b013976f029dcdd273916b12651540dcd5c293b 100644 (file)
@@ -53,6 +53,7 @@ extern struct formatstring_parser formatstring_c;
 extern struct formatstring_parser formatstring_python;
 extern struct formatstring_parser formatstring_lisp;
 extern struct formatstring_parser formatstring_java;
+extern struct formatstring_parser formatstring_pascal;
 extern struct formatstring_parser formatstring_ycp;
 
 /* Table of all format string parsers.  */
index 1a3cf466c41ac3cc7040bf31c517cce362127874..e9fe122d8222a1f8cd4d20a22dcbbb470ba89885 100644 (file)
@@ -45,6 +45,7 @@ const char *const format_language[NFORMATS] =
   /* format_python */  "python",
   /* format_lisp */    "lisp",
   /* format_java */    "java",
+  /* format_pascal */  "object-pascal",
   /* format_ycp */     "ycp"
 };
 
@@ -54,6 +55,7 @@ const char *const format_language_pretty[NFORMATS] =
   /* format_python */  "Python",
   /* format_lisp */    "Lisp",
   /* format_java */    "Java",
+  /* format_pascal */  "Object Pascal",
   /* format_ycp */     "YCP"
 };
 
index faa0154a39e17c0bf39541bf63ada89779fa8993..9a63bc344bf000190849f1026eb76210f84dff5a 100644 (file)
@@ -37,6 +37,7 @@ enum format_type
   format_python,
   format_lisp,
   format_java,
+  format_pascal,
   format_ycp,
   NFORMATS
 };
index 8e488d5fa02b007e56d7ed8b0f2e36f6b93bbba3..66049c19d59e46d85cea8c8631376bbad551bf19 100644 (file)
@@ -1,3 +1,9 @@
+2001-09-20  Bruno Haible  <haible@clisp.cons.org>
+
+       * format-pascal-1: New file.
+       * format-pascal-2: New file.
+       * Makefile.am (TESTS): Add them.
+
 2001-09-16  Bruno Haible  <haible@clisp.cons.org>
 
        * format-ycp-1: Use real YCP syntax.
index ca0ac72a43d7acd98d0ff6e8e4238c6783b3f45a..c261cf60060148bdb81fd8c8f38607c3b17a1543 100644 (file)
@@ -38,6 +38,7 @@ TESTS = gettext-1 gettext-2 \
        format-java-1 format-java-2 \
        format-lisp-1 format-lisp-2 \
        format-python-1 format-python-2 \
+       format-pascal-1 format-pascal-2 \
        format-ycp-1 format-ycp-2 \
        plural-1 plural-2
 
diff --git a/tests/format-pascal-1 b/tests/format-pascal-1
new file mode 100755 (executable)
index 0000000..6dad327
--- /dev/null
@@ -0,0 +1,126 @@
+#! /bin/sh
+
+# Test recognition of Object Pascal format strings.
+
+tmpfiles=""
+trap 'rm -fr $tmpfiles' 1 2 3 15
+
+tmpfiles="$tmpfiles f-op-1.data"
+cat <<\EOF > f-op-1.data
+# Valid: no argument
+"abc%%"
+# Valid: one string argument
+"abc%s"
+# Valid: one integer argument
+"abc%d"
+# Valid: one integer argument
+"abc%X"
+# Valid: one floating-point argument
+"abc%e"
+# Valid: one floating-point argument
+"abc%f"
+# Valid: one floating-point argument
+"abc%g"
+# Valid: one floating-point argument
+"abc%n"
+# Valid: one floating-point argument
+"abc%m"
+# Valid: one pointer argument
+"abc%p"
+# Valid: one argument with flags
+"abc%-g"
+# Valid: one argument with width
+"abc%2g"
+# Valid: one argument with width
+"abc%*g"
+# Valid: one argument with precision
+"abc%.4g"
+# Valid: one argument with precision
+"abc%.*g"
+# Valid: one argument with width and precision
+"abc%14.4g"
+# Valid: one argument with width and precision
+"abc%14.*g"
+# Valid: one argument with width and precision
+"abc%*.4g"
+# Valid: one argument with width and precision
+"abc%*.*g"
+# Invalid: unterminated
+"abc%"
+# Invalid: unknown format specifier
+"abc%y"
+# Invalid: flags after width
+"abc%*-g"
+# Invalid: twice precision
+"abc%.4.2g"
+# Valid: three arguments
+"abc%d%x%x"
+# Valid: a numbered argument
+"abc%0:d"
+# Valid: two-digit numbered arguments
+"abc%10:def%9:dgh%8:dij%7:dkl%6:dmn%5:dop%4:dqr%3:dst%2:duv%1:dwx%0:dyz"
+# Invalid: unterminated number
+"abc%1"
+# Invalid: flags before number
+"abc%-0:d"
+# Valid: three arguments, two with same number
+"abc%0:4e,%1:p,%0:g"
+# Invalid: argument with conflicting types
+"abc%0:4x,%1:p,%0:s"
+# Invalid: argument with conflicting types
+"abc%0:4e,%1:p,%0:d"
+# Valid: argument with different but not conflicting types
+"abc%0:4x,%1:p,%0:d"
+# Valid: mixing of numbered and unnumbered arguments
+"abc%d%1:x"
+# Valid: numbered argument with constant precision
+"abc%0:.9x"
+# Valid: mixing of numbered and unnumbered arguments
+"abc%3:.*x"
+# Valid: missing non-final argument
+"abc%1:x%3:s"
+# Valid: permutation
+"abc%1:ddef%0:d"
+# Valid: multiple uses of same argument
+"abc%2:xdef%1:pghi%2:x"
+# Valid: one argument with width
+"abc%1:*g"
+# Valid: one argument with width and precision
+"abc%2:*.*g"
+EOF
+
+: ${XGETTEXT=xgettext}
+n=0
+while read comment; do
+  read string
+  n=`expr $n + 1`
+  tmpfiles="$tmpfiles f-op-1-$n.in f-op-1-$n.po"
+  echo "x.y=${string}" | sed -e "s/\"/'/g" > f-op-1-$n.in
+  ${XGETTEXT} -L RST -o f-op-1-$n.po f-op-1-$n.in || exit 1
+  test -f f-op-1-$n.po || exit 1
+  fail=
+  if echo "$comment" | grep 'Valid:' > /dev/null; then
+    if grep object-pascal-format f-op-1-$n.po > /dev/null; then
+      :
+    else
+      fail=yes
+    fi
+  else
+    if grep object-pascal-format f-op-1-$n.po > /dev/null; then
+      fail=yes
+    else
+      :
+    fi
+  fi
+  if test -n "$fail"; then
+    echo "Format string recognition error:" 1>&2
+    cat f-op-1-$n.in 1>&2
+    echo "Got:" 1>&2
+    cat f-op-1-$n.po 1>&2
+    exit 1
+  fi
+done < f-op-1.data
+
+rm -fr $tmpfiles
+
+exit 0
diff --git a/tests/format-pascal-2 b/tests/format-pascal-2
new file mode 100755 (executable)
index 0000000..c03491d
--- /dev/null
@@ -0,0 +1,132 @@
+#! /bin/sh
+
+# Test checking of Object Pascal format strings.
+
+tmpfiles=""
+trap 'rm -fr $tmpfiles' 1 2 3 15
+
+tmpfiles="$tmpfiles f-op-2.data"
+cat <<\EOF > f-op-2.data
+# Valid: %% doesn't count
+msgid  "abc%%def"
+msgstr "xyz"
+# Invalid: invalid msgstr
+msgid  "abc%%def"
+msgstr "xyz%"
+# Valid: same arguments
+msgid  "abc%s%gdef"
+msgstr "xyz%s%g"
+# Valid: same arguments, with different widths
+msgid  "abc%2sdef"
+msgstr "xyz%3s"
+# Valid: same arguments but in numbered syntax
+msgid  "abc%s%gdef"
+msgstr "xyz%0:s%1:g"
+# Valid: permutation
+msgid  "abc%s%g%cdef"
+msgstr "xyz%2:c%1:g%0:s"
+# Invalid: too few arguments
+msgid  "abc%1:xdef%0:s"
+msgstr "xyz%0:s"
+# Invalid: too few arguments
+msgid  "abc%sdef%x"
+msgstr "xyz%s"
+# Invalid: too many arguments
+msgid  "abc%xdef"
+msgstr "xyz%xvw%p"
+# Valid: same numbered arguments, with different widths
+msgid  "abc%1:5s%0:4s"
+msgstr "xyz%1:4s%0:5s"
+# Invalid: missing argument
+msgid  "abc%1:sdef%0:x"
+msgstr "xyz%0:x"
+# Invalid: missing argument
+msgid  "abc%0:sdef%1:x"
+msgstr "xyz%1:x"
+# Invalid: added argument
+msgid  "abc%0:xdef"
+msgstr "xyz%0:xvw%1:p"
+# Valid: type compatibility
+msgid  "abc%e"
+msgstr "xyz%f"
+# Valid: type compatibility
+msgid  "abc%e"
+msgstr "xyz%g"
+# Valid: type compatibility
+msgid  "abc%e"
+msgstr "xyz%n"
+# Valid: type compatibility
+msgid  "abc%e"
+msgstr "xyz%m"
+# Invalid: type incompatibility
+msgid  "abc%d"
+msgstr "xyz%e"
+# Invalid: type incompatibility
+msgid  "abc%d"
+msgstr "xyz%s"
+# Invalid: type incompatibility
+msgid  "abc%d"
+msgstr "xyz%p"
+# Invalid: type incompatibility
+msgid  "abc%d"
+msgstr "xyz%x"
+# Invalid: type incompatibility
+msgid  "abc%e"
+msgstr "xyz%s"
+# Invalid: type incompatibility
+msgid  "abc%e"
+msgstr "xyz%p"
+# Invalid: type incompatibility
+msgid  "abc%e"
+msgstr "xyz%x"
+# Invalid: type incompatibility
+msgid  "abc%s"
+msgstr "xyz%p"
+# Invalid: type incompatibility
+msgid  "abc%s"
+msgstr "xyz%x"
+# Invalid: type incompatibility
+msgid  "abc%p"
+msgstr "xyz%x"
+# Invalid: type incompatibility for width
+msgid  "abc%g%*g"
+msgstr "xyz%*g%g"
+EOF
+
+: ${MSGFMT=msgfmt}
+n=0
+while read comment; do
+  read msgid_line
+  read msgstr_line
+  n=`expr $n + 1`
+  tmpfiles="$tmpfiles f-op-2-$n.po f-op-2-$n.mo"
+  cat <<EOF > f-op-2-$n.po
+#, object-pascal-format
+${msgid_line}
+${msgstr_line}
+EOF
+  fail=
+  if echo "$comment" | grep 'Valid:' > /dev/null; then
+    if ${MSGFMT} --check-format -o f-op-2-$n.mo f-op-2-$n.po; then
+      :
+    else
+      fail=yes
+    fi
+  else
+    ${MSGFMT} --check-format -o f-op-2-$n.mo f-op-2-$n.po 2> /dev/null
+    if test $? = 1; then
+      :
+    else
+      fail=yes
+    fi
+  fi
+  if test -n "$fail"; then
+    echo "Format string checking error:" 1>&2
+    cat f-op-2-$n.po 1>&2
+    exit 1
+  fi
+done < f-op-2.data
+
+rm -fr $tmpfiles
+
+exit 0