Last active
October 2, 2015 20:57
-
-
Save nox/2319273 to your computer and use it in GitHub Desktop.
Gettext support for Javascript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git c/gettext-tools/libgettextpo/Makefile.am w/gettext-tools/libgettextpo/Makefile.am | |
index a96c36d..245f333 100644 | |
--- c/gettext-tools/libgettextpo/Makefile.am | |
+++ w/gettext-tools/libgettextpo/Makefile.am | |
@@ -70,6 +70,7 @@ libgettextpo_la_AUXSOURCES = \ | |
../src/format-librep.c \ | |
../src/format-scheme.c \ | |
../src/format-java.c \ | |
+ ../src/format-javascript.c \ | |
../src/format-csharp.c \ | |
../src/format-awk.c \ | |
../src/format-pascal.c \ | |
diff --git c/gettext-tools/libgettextpo/Makefile.in w/gettext-tools/libgettextpo/Makefile.in | |
index 7488d45..544d664 100644 | |
--- c/gettext-tools/libgettextpo/Makefile.in | |
+++ w/gettext-tools/libgettextpo/Makefile.in | |
@@ -323,7 +323,7 @@ am__libgettextpo_la_SOURCES_DIST = gettext-po.c ../src/str-list.c \ | |
../src/plural-table.c ../src/format-c.c ../src/format-sh.c \ | |
../src/format-python.c ../src/format-lisp.c \ | |
../src/format-elisp.c ../src/format-librep.c \ | |
- ../src/format-scheme.c ../src/format-java.c \ | |
+ ../src/format-scheme.c ../src/format-java.c ../src/format-javascript.c \ | |
../src/format-csharp.c ../src/format-awk.c \ | |
../src/format-pascal.c ../src/format-ycp.c ../src/format-tcl.c \ | |
../src/format-perl.c ../src/format-perl-brace.c \ | |
@@ -339,7 +339,7 @@ am__objects_1 = str-list.lo dir-list.lo message.lo msgl-ascii.lo \ | |
read-po.lo read-catalog-abstract.lo read-catalog.lo \ | |
plural-table.lo format-c.lo format-sh.lo format-python.lo \ | |
format-lisp.lo format-elisp.lo format-librep.lo \ | |
- format-scheme.lo format-java.lo format-csharp.lo format-awk.lo \ | |
+ format-scheme.lo format-java.lo format-javascript.lo format-csharp.lo format-awk.lo \ | |
format-pascal.lo format-ycp.lo format-tcl.lo format-perl.lo \ | |
format-perl-brace.lo format-php.lo format-gcc-internal.lo \ | |
format-gfc-internal.lo format-qt.lo format-qt-plural.lo \ | |
@@ -1459,6 +1459,7 @@ libgettextpo_la_AUXSOURCES = \ | |
../src/format-librep.c \ | |
../src/format-scheme.c \ | |
../src/format-java.c \ | |
+ ../src/format-javascript.c \ | |
../src/format-csharp.c \ | |
../src/format-awk.c \ | |
../src/format-pascal.c \ | |
@@ -1725,6 +1726,10 @@ format-java.lo: ../src/format-java.c | |
$(AM_V_CC) @AM_BACKSLASH@ | |
$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o format-java.lo `test -f '../src/format-java.c' || echo '$(srcdir)/'`../src/format-java.c | |
+format-javascript.lo: ../src/format-javascript.c | |
+ $(AM_V_CC) @AM_BACKSLASH@ | |
+ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o format-javascript.lo `test -f '../src/format-javascript.c' || echo '$(srcdir)/'`../src/format-javascript.c | |
+ | |
format-csharp.lo: ../src/format-csharp.c | |
$(AM_V_CC) @AM_BACKSLASH@ | |
$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o format-csharp.lo `test -f '../src/format-csharp.c' || echo '$(srcdir)/'`../src/format-csharp.c | |
diff --git c/gettext-tools/src/Makefile.am w/gettext-tools/src/Makefile.am | |
index d9e7646..2f3b090 100644 | |
--- c/gettext-tools/src/Makefile.am | |
+++ w/gettext-tools/src/Makefile.am | |
@@ -51,7 +51,7 @@ write-qt.h \ | |
po-time.h plural-table.h lang-table.h format.h filters.h \ | |
xgettext.h x-c.h x-po.h x-sh.h x-python.h x-lisp.h x-elisp.h x-librep.h \ | |
x-scheme.h x-smalltalk.h x-java.h x-properties.h x-csharp.h x-awk.h x-ycp.h \ | |
-x-tcl.h x-perl.h x-php.h x-stringtable.h x-rst.h x-glade.h | |
+x-tcl.h x-perl.h x-php.h x-stringtable.h x-rst.h x-glade.h x-javascript.h | |
EXTRA_DIST += FILES project-id ChangeLog.0 | |
@@ -117,7 +117,7 @@ format-c.c format-sh.c format-python.c format-lisp.c format-elisp.c \ | |
format-librep.c format-scheme.c format-java.c format-csharp.c format-awk.c \ | |
format-pascal.c format-ycp.c format-tcl.c format-perl.c format-perl-brace.c \ | |
format-php.c format-gcc-internal.c format-gfc-internal.c \ | |
-format-qt.c format-qt-plural.c format-kde.c format-boost.c | |
+format-qt.c format-qt-plural.c format-kde.c format-boost.c format-javascript.c | |
# libgettextsrc contains all code that is needed by at least two programs. | |
libgettextsrc_la_SOURCES = \ | |
@@ -155,7 +155,7 @@ endif | |
xgettext_SOURCES += \ | |
x-c.c x-po.c x-sh.c x-python.c x-lisp.c x-elisp.c x-librep.c x-scheme.c \ | |
x-smalltalk.c x-java.c x-csharp.c x-awk.c x-ycp.c x-tcl.c x-perl.c x-php.c \ | |
- x-rst.c x-glade.c | |
+ x-rst.c x-glade.c x-javascript.c | |
if !WOE32DLL | |
msgattrib_SOURCES = msgattrib.c | |
else | |
diff --git c/gettext-tools/src/Makefile.in w/gettext-tools/src/Makefile.in | |
index f9842d2..457d524 100644 | |
--- c/gettext-tools/src/Makefile.in | |
+++ w/gettext-tools/src/Makefile.in | |
@@ -306,7 +306,7 @@ am__libgettextsrc_la_SOURCES_DIST = message.c po-error.c po-xerror.c \ | |
format-java.c format-csharp.c format-awk.c format-pascal.c \ | |
format-ycp.c format-tcl.c format-perl.c format-perl-brace.c \ | |
format-php.c format-gcc-internal.c format-gfc-internal.c \ | |
- format-qt.c format-qt-plural.c format-kde.c format-boost.c \ | |
+ format-qt.c format-qt-plural.c format-kde.c format-boost.c format-javascript.c \ | |
../woe32dll/c++format.cc ../woe32dll/gettextsrc-exports.c | |
am__objects_1 = message.lo po-error.lo po-xerror.lo \ | |
read-catalog-abstract.lo po-lex.lo po-gram-gen.lo \ | |
@@ -322,7 +322,7 @@ am__objects_1 = message.lo po-error.lo po-xerror.lo \ | |
@WOE32DLL_FALSE@ format-php.lo format-gcc-internal.lo \ | |
@WOE32DLL_FALSE@ format-gfc-internal.lo format-qt.lo \ | |
@WOE32DLL_FALSE@ format-qt-plural.lo format-kde.lo \ | |
-@WOE32DLL_FALSE@ format-boost.lo | |
+@WOE32DLL_FALSE@ format-boost.lo format-javascript.lo | |
@WOE32DLL_TRUE@am__objects_2 = c++format.lo format-c.lo format-sh.lo \ | |
@WOE32DLL_TRUE@ format-python.lo format-lisp.lo format-elisp.lo \ | |
@WOE32DLL_TRUE@ format-librep.lo format-scheme.lo \ | |
@@ -332,7 +332,7 @@ am__objects_1 = message.lo po-error.lo po-xerror.lo \ | |
@WOE32DLL_TRUE@ format-php.lo format-gcc-internal.lo \ | |
@WOE32DLL_TRUE@ format-gfc-internal.lo format-qt.lo \ | |
@WOE32DLL_TRUE@ format-qt-plural.lo format-kde.lo \ | |
-@WOE32DLL_TRUE@ format-boost.lo | |
+@WOE32DLL_TRUE@ format-boost.lo format-javascript.lo | |
@WOE32DLL_TRUE@am__objects_3 = gettextsrc-exports.lo | |
am_libgettextsrc_la_OBJECTS = $(am__objects_1) read-catalog.lo \ | |
color.lo write-catalog.lo write-properties.lo \ | |
@@ -454,7 +454,7 @@ urlget_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ | |
am__xgettext_SOURCES_DIST = xgettext.c x-c.c x-po.c x-sh.c x-python.c \ | |
x-lisp.c x-elisp.c x-librep.c x-scheme.c x-smalltalk.c \ | |
x-java.c x-csharp.c x-awk.c x-ycp.c x-tcl.c x-perl.c x-php.c \ | |
- x-rst.c x-glade.c ../woe32dll/c++xgettext.cc | |
+ x-rst.c x-glade.c x-javascript.c ../woe32dll/c++xgettext.cc | |
@WOE32DLL_FALSE@am_xgettext_OBJECTS = xgettext-xgettext.$(OBJEXT) \ | |
@WOE32DLL_FALSE@ xgettext-x-c.$(OBJEXT) xgettext-x-po.$(OBJEXT) \ | |
@WOE32DLL_FALSE@ xgettext-x-sh.$(OBJEXT) \ | |
@@ -472,7 +472,8 @@ am__xgettext_SOURCES_DIST = xgettext.c x-c.c x-po.c x-sh.c x-python.c \ | |
@WOE32DLL_FALSE@ xgettext-x-perl.$(OBJEXT) \ | |
@WOE32DLL_FALSE@ xgettext-x-php.$(OBJEXT) \ | |
@WOE32DLL_FALSE@ xgettext-x-rst.$(OBJEXT) \ | |
-@WOE32DLL_FALSE@ xgettext-x-glade.$(OBJEXT) | |
+@WOE32DLL_FALSE@ xgettext-x-glade.$(OBJEXT) \ | |
+@WOE32DLL_FALSE@ xgettext-x-javascript.$(OBJEXT) | |
@WOE32DLL_TRUE@am_xgettext_OBJECTS = xgettext-c++xgettext.$(OBJEXT) \ | |
@WOE32DLL_TRUE@ xgettext-x-c.$(OBJEXT) xgettext-x-po.$(OBJEXT) \ | |
@WOE32DLL_TRUE@ xgettext-x-sh.$(OBJEXT) \ | |
@@ -490,7 +491,8 @@ am__xgettext_SOURCES_DIST = xgettext.c x-c.c x-po.c x-sh.c x-python.c \ | |
@WOE32DLL_TRUE@ xgettext-x-perl.$(OBJEXT) \ | |
@WOE32DLL_TRUE@ xgettext-x-php.$(OBJEXT) \ | |
@WOE32DLL_TRUE@ xgettext-x-rst.$(OBJEXT) \ | |
-@WOE32DLL_TRUE@ xgettext-x-glade.$(OBJEXT) | |
+@WOE32DLL_TRUE@ xgettext-x-glade.$(OBJEXT) \ | |
+@WOE32DLL_TRUE@ xgettext-x-javascript.$(OBJEXT) | |
xgettext_OBJECTS = $(am_xgettext_OBJECTS) | |
DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) | |
depcomp = | |
@@ -1489,7 +1491,7 @@ write-qt.h \ | |
po-time.h plural-table.h lang-table.h format.h filters.h \ | |
xgettext.h x-c.h x-po.h x-sh.h x-python.h x-lisp.h x-elisp.h x-librep.h \ | |
x-scheme.h x-smalltalk.h x-java.h x-properties.h x-csharp.h x-awk.h x-ycp.h \ | |
-x-tcl.h x-perl.h x-php.h x-stringtable.h x-rst.h x-glade.h | |
+x-tcl.h x-perl.h x-php.h x-stringtable.h x-rst.h x-glade.h x-javascript.h | |
aliaspath = $(localedir) | |
jardir = $(datadir)/gettext | |
@@ -1531,17 +1533,17 @@ dir-list.c str-list.c | |
@WOE32DLL_FALSE@ format-perl.c format-perl-brace.c format-php.c \ | |
@WOE32DLL_FALSE@ format-gcc-internal.c format-gfc-internal.c \ | |
@WOE32DLL_FALSE@ format-qt.c format-qt-plural.c format-kde.c \ | |
-@WOE32DLL_FALSE@ format-boost.c | |
+@WOE32DLL_FALSE@ format-boost.c format-javascript.c | |
@WOE32DLL_TRUE@FORMAT_SOURCE = ../woe32dll/c++format.cc \ | |
@WOE32DLL_TRUE@ format-invalid.h format-c.c format-sh.c \ | |
@WOE32DLL_TRUE@ format-python.c format-lisp.c format-elisp.c \ | |
-@WOE32DLL_TRUE@ format-librep.c format-scheme.c format-java.c \ | |
+@WOE32DLL_TRUE@ format-librep.c format-scheme.c format-javascript.c \ | |
@WOE32DLL_TRUE@ format-csharp.c format-awk.c format-pascal.c \ | |
@WOE32DLL_TRUE@ format-ycp.c format-tcl.c format-perl.c \ | |
@WOE32DLL_TRUE@ format-perl-brace.c format-php.c \ | |
@WOE32DLL_TRUE@ format-gcc-internal.c format-gfc-internal.c \ | |
@WOE32DLL_TRUE@ format-qt.c format-qt-plural.c format-kde.c \ | |
-@WOE32DLL_TRUE@ format-boost.c | |
+@WOE32DLL_TRUE@ format-boost.c format-javascript.c | |
# libgettextsrc contains all code that is needed by at least two programs. | |
libgettextsrc_la_SOURCES = $(COMMON_SOURCE) read-catalog.c color.c \ | |
@@ -1569,12 +1571,12 @@ msgunfmt_SOURCES = msgunfmt.c read-mo.c read-java.c read-csharp.c \ | |
@WOE32DLL_FALSE@ x-python.c x-lisp.c x-elisp.c x-librep.c \ | |
@WOE32DLL_FALSE@ x-scheme.c x-smalltalk.c x-java.c x-csharp.c \ | |
@WOE32DLL_FALSE@ x-awk.c x-ycp.c x-tcl.c x-perl.c x-php.c \ | |
-@WOE32DLL_FALSE@ x-rst.c x-glade.c | |
+@WOE32DLL_FALSE@ x-rst.c x-glade.c x-javascript.c | |
@WOE32DLL_TRUE@xgettext_SOURCES = ../woe32dll/c++xgettext.cc x-c.c \ | |
@WOE32DLL_TRUE@ x-po.c x-sh.c x-python.c x-lisp.c x-elisp.c \ | |
@WOE32DLL_TRUE@ x-librep.c x-scheme.c x-smalltalk.c x-java.c \ | |
@WOE32DLL_TRUE@ x-csharp.c x-awk.c x-ycp.c x-tcl.c x-perl.c \ | |
-@WOE32DLL_TRUE@ x-php.c x-rst.c x-glade.c | |
+@WOE32DLL_TRUE@ x-php.c x-rst.c x-glade.c x-javascript.c | |
@WOE32DLL_FALSE@msgattrib_SOURCES = msgattrib.c | |
@WOE32DLL_TRUE@msgattrib_SOURCES = ../woe32dll/c++msgattrib.cc | |
@WOE32DLL_FALSE@msgcat_SOURCES = msgcat.c | |
@@ -2507,6 +2509,14 @@ xgettext-x-glade.obj: x-glade.c | |
$(AM_V_CC) @AM_BACKSLASH@ | |
$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-glade.obj `if test -f 'x-glade.c'; then $(CYGPATH_W) 'x-glade.c'; else $(CYGPATH_W) '$(srcdir)/x-glade.c'; fi` | |
+xgettext-x-javascript.o: x-javascript.c | |
+ $(AM_V_CC) @AM_BACKSLASH@ | |
+ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-javascript.o `test -f 'x-javascript.c' || echo '$(srcdir)/'`x-javascript.c | |
+ | |
+xgettext-x-javascript.obj: x-javascript.c | |
+ $(AM_V_CC) @AM_BACKSLASH@ | |
+ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-javascript.obj `if test -f 'x-javascript.c'; then $(CYGPATH_W) 'x-javascript.c'; else $(CYGPATH_W) '$(srcdir)/x-javascript.c'; fi` | |
+ | |
.cc.o: | |
$(AM_V_CXX) @AM_BACKSLASH@ | |
$(CXXCOMPILE) -c -o $@ $< | |
diff --git c/gettext-tools/src/format-javascript.c w/gettext-tools/src/format-javascript.c | |
new file mode 100644 | |
index 0000000..5705972 | |
--- /dev/null | |
+++ w/gettext-tools/src/format-javascript.c | |
@@ -0,0 +1,669 @@ | |
+/* JavaScript format strings. | |
+ Copyright (C) 2001-2004, 2006-2009 Free Software Foundation, Inc. | |
+ Written by Andreas Stricker <[email protected]>, 2010. | |
+ It's based on python format module from Bruno Haible. | |
+ | |
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>. */ | |
+ | |
+#ifdef HAVE_CONFIG_H | |
+# include <config.h> | |
+#endif | |
+ | |
+#include <stdbool.h> | |
+#include <stdlib.h> | |
+#include <string.h> | |
+ | |
+#include "format.h" | |
+#include "c-ctype.h" | |
+#include "xalloc.h" | |
+#include "xvasprintf.h" | |
+#include "format-invalid.h" | |
+#include "gettext.h" | |
+ | |
+#define _(str) gettext (str) | |
+ | |
+/* Gettext itself doesn't know any format string. But there are | |
+ plenty of libraries using any kind of format strings: | |
+ * JS-Gettext allows %1 %2 ... | |
+ * Prototype JS has templates #{name} | |
+ * ExtJS has String.format with {1} {2} ... and Templates with {name} | |
+ */ | |
+ | |
+enum format_arg_type | |
+{ | |
+ FAT_NONE, | |
+ FAT_ANY, | |
+ FAT_CHARACTER, | |
+ FAT_STRING, | |
+ FAT_INTEGER, | |
+ FAT_FLOAT | |
+}; | |
+ | |
+struct named_arg | |
+{ | |
+ char *name; | |
+ enum format_arg_type type; | |
+}; | |
+ | |
+struct unnamed_arg | |
+{ | |
+ enum format_arg_type type; | |
+}; | |
+ | |
+struct spec | |
+{ | |
+ unsigned int directives; | |
+ unsigned int named_arg_count; | |
+ unsigned int unnamed_arg_count; | |
+ unsigned int allocated; | |
+ struct named_arg *named; | |
+ struct unnamed_arg *unnamed; | |
+}; | |
+ | |
+/* 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) | |
+ | |
+ | |
+static int | |
+named_arg_compare (const void *p1, const void *p2) | |
+{ | |
+ return strcmp (((const struct named_arg *) p1)->name, | |
+ ((const struct named_arg *) p2)->name); | |
+} | |
+ | |
+#define INVALID_MIXES_NAMED_UNNAMED() \ | |
+ xstrdup (_("The string refers to arguments both through argument names and through unnamed argument specifications.")) | |
+ | |
+static void * | |
+format_parse (const char *format, bool translated, char *fdi, | |
+ char **invalid_reason) | |
+{ | |
+ const char *const format_start = format; | |
+ struct spec spec; | |
+ struct spec *result; | |
+ | |
+ spec.directives = 0; | |
+ spec.named_arg_count = 0; | |
+ spec.unnamed_arg_count = 0; | |
+ spec.allocated = 0; | |
+ spec.named = NULL; | |
+ spec.unnamed = NULL; | |
+ | |
+ for (; *format != '\0';) | |
+ if (*format++ == '%') | |
+ { | |
+ /* A directive. */ | |
+ char *name = NULL; | |
+ bool zero_precision = false; | |
+ enum format_arg_type type; | |
+ | |
+ FDI_SET (format - 1, FMTDIR_START); | |
+ spec.directives++; | |
+ | |
+ if (*format == '(') | |
+ { | |
+ unsigned int depth; | |
+ const char *name_start; | |
+ const char *name_end; | |
+ size_t n; | |
+ | |
+ name_start = ++format; | |
+ depth = 0; | |
+ for (; *format != '\0'; format++) | |
+ { | |
+ if (*format == '(') | |
+ depth++; | |
+ else if (*format == ')') | |
+ { | |
+ if (depth == 0) | |
+ break; | |
+ else | |
+ depth--; | |
+ } | |
+ } | |
+ if (*format == '\0') | |
+ { | |
+ *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); | |
+ FDI_SET (format - 1, FMTDIR_ERROR); | |
+ goto bad_format; | |
+ } | |
+ name_end = format++; | |
+ | |
+ n = name_end - name_start; | |
+ name = XNMALLOC (n + 1, char); | |
+ memcpy (name, name_start, n); | |
+ name[n] = '\0'; | |
+ } | |
+ | |
+ while (*format == '-' || *format == '+' || *format == ' ' | |
+ || *format == '#' || *format == '0') | |
+ format++; | |
+ | |
+ if (*format == '*') | |
+ { | |
+ format++; | |
+ | |
+ /* Named and unnamed specifications are exclusive. */ | |
+ if (spec.named_arg_count > 0) | |
+ { | |
+ *invalid_reason = INVALID_MIXES_NAMED_UNNAMED (); | |
+ FDI_SET (format - 1, FMTDIR_ERROR); | |
+ goto bad_format; | |
+ } | |
+ | |
+ if (spec.allocated == spec.unnamed_arg_count) | |
+ { | |
+ spec.allocated = 2 * spec.allocated + 1; | |
+ spec.unnamed = (struct unnamed_arg *) xrealloc (spec.unnamed, spec.allocated * sizeof (struct unnamed_arg)); | |
+ } | |
+ spec.unnamed[spec.unnamed_arg_count].type = FAT_INTEGER; | |
+ spec.unnamed_arg_count++; | |
+ } | |
+ else if (isdigit (*format)) | |
+ { | |
+ do format++; while (isdigit (*format)); | |
+ } | |
+ | |
+ if (*format == '.') | |
+ { | |
+ format++; | |
+ | |
+ if (*format == '*') | |
+ { | |
+ format++; | |
+ | |
+ /* Named and unnamed specifications are exclusive. */ | |
+ if (spec.named_arg_count > 0) | |
+ { | |
+ *invalid_reason = INVALID_MIXES_NAMED_UNNAMED (); | |
+ FDI_SET (format - 1, FMTDIR_ERROR); | |
+ goto bad_format; | |
+ } | |
+ | |
+ if (spec.allocated == spec.unnamed_arg_count) | |
+ { | |
+ spec.allocated = 2 * spec.allocated + 1; | |
+ spec.unnamed = (struct unnamed_arg *) xrealloc (spec.unnamed, spec.allocated * sizeof (struct unnamed_arg)); | |
+ } | |
+ spec.unnamed[spec.unnamed_arg_count].type = FAT_INTEGER; | |
+ spec.unnamed_arg_count++; | |
+ } | |
+ else if (isdigit (*format)) | |
+ { | |
+ zero_precision = true; | |
+ do | |
+ { | |
+ if (*format != '0') | |
+ zero_precision = false; | |
+ format++; | |
+ } | |
+ while (isdigit (*format)); | |
+ } | |
+ } | |
+ | |
+ if (*format == 'h' || *format == 'l' || *format == 'L') | |
+ format++; | |
+ | |
+ switch (*format) | |
+ { | |
+ case '%': | |
+ type = FAT_NONE; | |
+ break; | |
+ case 'c': | |
+ type = FAT_CHARACTER; | |
+ break; | |
+ case 's': case 'r': | |
+ type = (zero_precision ? FAT_ANY : FAT_STRING); | |
+ break; | |
+ case 'i': case 'd': case 'u': case 'o': case 'x': case 'X': | |
+ type = FAT_INTEGER; | |
+ break; | |
+ case 'e': case 'E': case 'f': case 'g': case 'G': | |
+ type = FAT_FLOAT; | |
+ break; | |
+ default: | |
+ if (*format == '\0') | |
+ { | |
+ *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); | |
+ FDI_SET (format - 1, FMTDIR_ERROR); | |
+ } | |
+ else | |
+ { | |
+ *invalid_reason = | |
+ INVALID_CONVERSION_SPECIFIER (spec.directives, *format); | |
+ FDI_SET (format, FMTDIR_ERROR); | |
+ } | |
+ goto bad_format; | |
+ } | |
+ | |
+ if (name != NULL) | |
+ { | |
+ /* Named argument. */ | |
+ | |
+ /* Named and unnamed specifications are exclusive. */ | |
+ if (spec.unnamed_arg_count > 0) | |
+ { | |
+ *invalid_reason = INVALID_MIXES_NAMED_UNNAMED (); | |
+ FDI_SET (format, FMTDIR_ERROR); | |
+ goto bad_format; | |
+ } | |
+ | |
+ if (spec.allocated == spec.named_arg_count) | |
+ { | |
+ spec.allocated = 2 * spec.allocated + 1; | |
+ spec.named = (struct named_arg *) xrealloc (spec.named, spec.allocated * sizeof (struct named_arg)); | |
+ } | |
+ spec.named[spec.named_arg_count].name = name; | |
+ spec.named[spec.named_arg_count].type = type; | |
+ spec.named_arg_count++; | |
+ } | |
+ else if (*format != '%') | |
+ { | |
+ /* Unnamed argument. */ | |
+ | |
+ /* Named and unnamed specifications are exclusive. */ | |
+ if (spec.named_arg_count > 0) | |
+ { | |
+ *invalid_reason = INVALID_MIXES_NAMED_UNNAMED (); | |
+ FDI_SET (format, FMTDIR_ERROR); | |
+ goto bad_format; | |
+ } | |
+ | |
+ if (spec.allocated == spec.unnamed_arg_count) | |
+ { | |
+ spec.allocated = 2 * spec.allocated + 1; | |
+ spec.unnamed = (struct unnamed_arg *) xrealloc (spec.unnamed, spec.allocated * sizeof (struct unnamed_arg)); | |
+ } | |
+ spec.unnamed[spec.unnamed_arg_count].type = type; | |
+ spec.unnamed_arg_count++; | |
+ } | |
+ | |
+ FDI_SET (format, FMTDIR_END); | |
+ | |
+ format++; | |
+ } | |
+ | |
+ /* Sort the named argument array, and eliminate duplicates. */ | |
+ if (spec.named_arg_count > 1) | |
+ { | |
+ unsigned int i, j; | |
+ bool err; | |
+ | |
+ qsort (spec.named, spec.named_arg_count, sizeof (struct named_arg), | |
+ named_arg_compare); | |
+ | |
+ /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */ | |
+ err = false; | |
+ for (i = j = 0; i < spec.named_arg_count; i++) | |
+ if (j > 0 && strcmp (spec.named[i].name, spec.named[j-1].name) == 0) | |
+ { | |
+ enum format_arg_type type1 = spec.named[i].type; | |
+ enum format_arg_type type2 = spec.named[j-1].type; | |
+ enum format_arg_type type_both; | |
+ | |
+ if (type1 == type2 || type2 == FAT_ANY) | |
+ type_both = type1; | |
+ else if (type1 == FAT_ANY) | |
+ type_both = type2; | |
+ else | |
+ { | |
+ /* Incompatible types. */ | |
+ type_both = FAT_NONE; | |
+ if (!err) | |
+ *invalid_reason = | |
+ xasprintf (_("The string refers to the argument named '%s' in incompatible ways."), spec.named[i].name); | |
+ err = true; | |
+ } | |
+ | |
+ spec.named[j-1].type = type_both; | |
+ free (spec.named[i].name); | |
+ } | |
+ else | |
+ { | |
+ if (j < i) | |
+ { | |
+ spec.named[j].name = spec.named[i].name; | |
+ spec.named[j].type = spec.named[i].type; | |
+ } | |
+ j++; | |
+ } | |
+ spec.named_arg_count = j; | |
+ if (err) | |
+ /* *invalid_reason has already been set above. */ | |
+ goto bad_format; | |
+ } | |
+ | |
+ result = XMALLOC (struct spec); | |
+ *result = spec; | |
+ return result; | |
+ | |
+ bad_format: | |
+ if (spec.named != NULL) | |
+ { | |
+ unsigned int i; | |
+ for (i = 0; i < spec.named_arg_count; i++) | |
+ free (spec.named[i].name); | |
+ free (spec.named); | |
+ } | |
+ if (spec.unnamed != NULL) | |
+ free (spec.unnamed); | |
+ return NULL; | |
+} | |
+ | |
+static void | |
+format_free (void *descr) | |
+{ | |
+ struct spec *spec = (struct spec *) descr; | |
+ | |
+ if (spec->named != NULL) | |
+ { | |
+ unsigned int i; | |
+ for (i = 0; i < spec->named_arg_count; i++) | |
+ free (spec->named[i].name); | |
+ free (spec->named); | |
+ } | |
+ if (spec->unnamed != NULL) | |
+ free (spec->unnamed); | |
+ free (spec); | |
+} | |
+ | |
+static int | |
+format_get_number_of_directives (void *descr) | |
+{ | |
+ struct spec *spec = (struct spec *) descr; | |
+ | |
+ return spec->directives; | |
+} | |
+ | |
+static bool | |
+format_check (void *msgid_descr, void *msgstr_descr, bool equality, | |
+ formatstring_error_logger_t error_logger, | |
+ const char *pretty_msgid, const char *pretty_msgstr) | |
+{ | |
+ struct spec *spec1 = (struct spec *) msgid_descr; | |
+ struct spec *spec2 = (struct spec *) msgstr_descr; | |
+ bool err = false; | |
+ | |
+ if (spec1->named_arg_count > 0 && spec2->unnamed_arg_count > 0) | |
+ { | |
+ if (error_logger) | |
+ error_logger (_("format specifications in '%s' expect a mapping, those in '%s' expect a tuple"), | |
+ pretty_msgid, pretty_msgstr); | |
+ err = true; | |
+ } | |
+ else if (spec1->unnamed_arg_count > 0 && spec2->named_arg_count > 0) | |
+ { | |
+ if (error_logger) | |
+ error_logger (_("format specifications in '%s' expect a tuple, those in '%s' expect a mapping"), | |
+ pretty_msgid, pretty_msgstr); | |
+ err = true; | |
+ } | |
+ else | |
+ { | |
+ if (spec1->named_arg_count + spec2->named_arg_count > 0) | |
+ { | |
+ unsigned int i, j; | |
+ unsigned int n1 = spec1->named_arg_count; | |
+ unsigned int n2 = spec2->named_arg_count; | |
+ | |
+ /* Check the argument names are the same. | |
+ Both arrays are sorted. We search for the first difference. */ | |
+ for (i = 0, j = 0; i < n1 || j < n2; ) | |
+ { | |
+ int cmp = (i >= n1 ? 1 : | |
+ j >= n2 ? -1 : | |
+ strcmp (spec1->named[i].name, spec2->named[j].name)); | |
+ | |
+ if (cmp > 0) | |
+ { | |
+ if (error_logger) | |
+ error_logger (_("a format specification for argument '%s', as in '%s', doesn't exist in '%s'"), | |
+ spec2->named[j].name, pretty_msgstr, | |
+ pretty_msgid); | |
+ err = true; | |
+ break; | |
+ } | |
+ else if (cmp < 0) | |
+ { | |
+ if (equality) | |
+ { | |
+ if (error_logger) | |
+ error_logger (_("a format specification for argument '%s' doesn't exist in '%s'"), | |
+ spec1->named[i].name, pretty_msgstr); | |
+ err = true; | |
+ break; | |
+ } | |
+ else | |
+ i++; | |
+ } | |
+ else | |
+ j++, i++; | |
+ } | |
+ /* Check the argument types are the same. */ | |
+ if (!err) | |
+ for (i = 0, j = 0; j < n2; ) | |
+ { | |
+ if (strcmp (spec1->named[i].name, spec2->named[j].name) == 0) | |
+ { | |
+ if (!(spec1->named[i].type == spec2->named[j].type | |
+ || (!equality | |
+ && (spec1->named[i].type == FAT_ANY | |
+ || spec2->named[j].type == FAT_ANY)))) | |
+ { | |
+ if (error_logger) | |
+ error_logger (_("format specifications in '%s' and '%s' for argument '%s' are not the same"), | |
+ pretty_msgid, pretty_msgstr, | |
+ spec2->named[j].name); | |
+ err = true; | |
+ break; | |
+ } | |
+ j++, i++; | |
+ } | |
+ else | |
+ i++; | |
+ } | |
+ } | |
+ | |
+ if (spec1->unnamed_arg_count + spec2->unnamed_arg_count > 0) | |
+ { | |
+ unsigned int i; | |
+ | |
+ /* Check the argument types are the same. */ | |
+ if (spec1->unnamed_arg_count != spec2->unnamed_arg_count) | |
+ { | |
+ if (error_logger) | |
+ error_logger (_("number of format specifications in '%s' and '%s' does not match"), | |
+ pretty_msgid, pretty_msgstr); | |
+ err = true; | |
+ } | |
+ else | |
+ for (i = 0; i < spec2->unnamed_arg_count; i++) | |
+ if (!(spec1->unnamed[i].type == spec2->unnamed[i].type | |
+ || (!equality | |
+ && (spec1->unnamed[i].type == FAT_ANY | |
+ || spec2->unnamed[i].type == FAT_ANY)))) | |
+ { | |
+ if (error_logger) | |
+ error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"), | |
+ pretty_msgid, pretty_msgstr, i + 1); | |
+ err = true; | |
+ } | |
+ } | |
+ } | |
+ | |
+ return err; | |
+} | |
+ | |
+ | |
+struct formatstring_parser formatstring_javascript = | |
+{ | |
+ format_parse, | |
+ format_free, | |
+ format_get_number_of_directives, | |
+ NULL, | |
+ format_check | |
+}; | |
+ | |
+ | |
+unsigned int | |
+get_javascript_format_unnamed_arg_count (const char *string) | |
+{ | |
+ /* Parse the format string. */ | |
+ char *invalid_reason = NULL; | |
+ struct spec *descr = | |
+ (struct spec *) format_parse (string, false, NULL, &invalid_reason); | |
+ | |
+ if (descr != NULL) | |
+ { | |
+ unsigned int result = descr->unnamed_arg_count; | |
+ | |
+ format_free (descr); | |
+ return result; | |
+ } | |
+ else | |
+ { | |
+ free (invalid_reason); | |
+ return 0; | |
+ } | |
+} | |
+ | |
+ | |
+#ifdef TEST | |
+ | |
+/* Test program: Print the argument list specification returned by | |
+ format_parse for strings read from standard input. */ | |
+ | |
+#include <stdio.h> | |
+ | |
+static void | |
+format_print (void *descr) | |
+{ | |
+ struct spec *spec = (struct spec *) descr; | |
+ unsigned int i; | |
+ | |
+ if (spec == NULL) | |
+ { | |
+ printf ("INVALID"); | |
+ return; | |
+ } | |
+ | |
+ if (spec->named_arg_count > 0) | |
+ { | |
+ if (spec->unnamed_arg_count > 0) | |
+ abort (); | |
+ | |
+ printf ("{"); | |
+ for (i = 0; i < spec->named_arg_count; i++) | |
+ { | |
+ if (i > 0) | |
+ printf (", "); | |
+ printf ("'%s':", spec->named[i].name); | |
+ switch (spec->named[i].type) | |
+ { | |
+ case FAT_ANY: | |
+ printf ("*"); | |
+ break; | |
+ case FAT_CHARACTER: | |
+ printf ("c"); | |
+ break; | |
+ case FAT_STRING: | |
+ printf ("s"); | |
+ break; | |
+ case FAT_INTEGER: | |
+ printf ("i"); | |
+ break; | |
+ case FAT_FLOAT: | |
+ printf ("f"); | |
+ break; | |
+ default: | |
+ abort (); | |
+ } | |
+ } | |
+ printf ("}"); | |
+ } | |
+ else | |
+ { | |
+ printf ("("); | |
+ for (i = 0; i < spec->unnamed_arg_count; i++) | |
+ { | |
+ if (i > 0) | |
+ printf (" "); | |
+ switch (spec->unnamed[i].type) | |
+ { | |
+ case FAT_ANY: | |
+ printf ("*"); | |
+ break; | |
+ case FAT_CHARACTER: | |
+ printf ("c"); | |
+ break; | |
+ case FAT_STRING: | |
+ printf ("s"); | |
+ break; | |
+ case FAT_INTEGER: | |
+ printf ("i"); | |
+ break; | |
+ case FAT_FLOAT: | |
+ printf ("f"); | |
+ break; | |
+ default: | |
+ abort (); | |
+ } | |
+ } | |
+ printf (")"); | |
+ } | |
+} | |
+ | |
+int | |
+main () | |
+{ | |
+ for (;;) | |
+ { | |
+ char *line = NULL; | |
+ size_t line_size = 0; | |
+ int line_len; | |
+ char *invalid_reason; | |
+ void *descr; | |
+ | |
+ line_len = getline (&line, &line_size, stdin); | |
+ if (line_len < 0) | |
+ break; | |
+ if (line_len > 0 && line[line_len - 1] == '\n') | |
+ line[--line_len] = '\0'; | |
+ | |
+ invalid_reason = NULL; | |
+ descr = format_parse (line, false, NULL, &invalid_reason); | |
+ | |
+ format_print (descr); | |
+ printf ("\n"); | |
+ if (descr == NULL) | |
+ printf ("%s\n", invalid_reason); | |
+ | |
+ free (invalid_reason); | |
+ free (line); | |
+ } | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * For Emacs M-x compile | |
+ * Local Variables: | |
+ * compile-command: "/bin/sh ../libtool --tag=CC --mode=link gcc -o a.out -static -O -g -Wall -I.. -I../gnulib-lib -I../intl -DHAVE_CONFIG_H -DTEST format-javascript.c ../gnulib-lib/libgettextlib.la" | |
+ * End: | |
+ */ | |
+ | |
+#endif /* TEST */ | |
diff --git c/gettext-tools/src/format.c w/gettext-tools/src/format.c | |
index e6c5de9..22fc8c2 100644 | |
--- c/gettext-tools/src/format.c | |
+++ w/gettext-tools/src/format.c | |
@@ -44,6 +44,7 @@ struct formatstring_parser *formatstring_parsers[NFORMATS] = | |
/* format_scheme */ &formatstring_scheme, | |
/* format_smalltalk */ &formatstring_smalltalk, | |
/* format_java */ &formatstring_java, | |
+ /* format_javascript */ &formatstring_javascript, | |
/* format_csharp */ &formatstring_csharp, | |
/* format_awk */ &formatstring_awk, | |
/* format_pascal */ &formatstring_pascal, | |
diff --git c/gettext-tools/src/format.h w/gettext-tools/src/format.h | |
index 60f0adc..679cd9a 100644 | |
--- c/gettext-tools/src/format.h | |
+++ w/gettext-tools/src/format.h | |
@@ -105,6 +105,7 @@ extern DLL_VARIABLE struct formatstring_parser formatstring_librep; | |
extern DLL_VARIABLE struct formatstring_parser formatstring_scheme; | |
extern DLL_VARIABLE struct formatstring_parser formatstring_smalltalk; | |
extern DLL_VARIABLE struct formatstring_parser formatstring_java; | |
+extern DLL_VARIABLE struct formatstring_parser formatstring_javascript; | |
extern DLL_VARIABLE struct formatstring_parser formatstring_csharp; | |
extern DLL_VARIABLE struct formatstring_parser formatstring_awk; | |
extern DLL_VARIABLE struct formatstring_parser formatstring_pascal; | |
diff --git c/gettext-tools/src/message.c w/gettext-tools/src/message.c | |
index 5162b06..e57ddab 100644 | |
--- c/gettext-tools/src/message.c | |
+++ w/gettext-tools/src/message.c | |
@@ -44,6 +44,7 @@ const char *const format_language[NFORMATS] = | |
/* format_scheme */ "scheme", | |
/* format_smalltalk */ "smalltalk", | |
/* format_java */ "java", | |
+ /* format_javascript */ "javascript", | |
/* format_csharp */ "csharp", | |
/* format_awk */ "awk", | |
/* format_pascal */ "object-pascal", | |
@@ -72,6 +73,7 @@ const char *const format_language_pretty[NFORMATS] = | |
/* format_scheme */ "Scheme", | |
/* format_smalltalk */ "Smalltalk", | |
/* format_java */ "Java", | |
+ /* format_javascript */ "JavaScript", | |
/* format_csharp */ "C#", | |
/* format_awk */ "awk", | |
/* format_pascal */ "Object Pascal", | |
diff --git c/gettext-tools/src/message.h w/gettext-tools/src/message.h | |
index af9244a..0f200b1 100644 | |
--- c/gettext-tools/src/message.h | |
+++ w/gettext-tools/src/message.h | |
@@ -53,6 +53,7 @@ enum format_type | |
format_scheme, | |
format_smalltalk, | |
format_java, | |
+ format_javascript, | |
format_csharp, | |
format_awk, | |
format_pascal, | |
@@ -68,7 +69,7 @@ enum format_type | |
format_kde, | |
format_boost | |
}; | |
-#define NFORMATS 24 /* Number of format_type enum values. */ | |
+#define NFORMATS 25 /* Number of format_type enum values. */ | |
extern DLL_VARIABLE const char *const format_language[NFORMATS]; | |
extern DLL_VARIABLE const char *const format_language_pretty[NFORMATS]; | |
diff --git c/gettext-tools/src/x-javascript.c w/gettext-tools/src/x-javascript.c | |
new file mode 100644 | |
index 0000000..f97308c | |
--- /dev/null | |
+++ w/gettext-tools/src/x-javascript.c | |
@@ -0,0 +1,1809 @@ | |
+/* xgettext JavaScript backend. | |
+ Copyright (C) 2002-2003, 2005-2009 Free Software Foundation, Inc. | |
+ | |
+ This file was written by Andreas Stricker <[email protected]>, 2010 | |
+ It's based on x-python from Bruno Haible. | |
+ | |
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>. */ | |
+ | |
+#ifdef HAVE_CONFIG_H | |
+# include "config.h" | |
+#endif | |
+ | |
+/* Specification. */ | |
+#include "x-javascript.h" | |
+ | |
+#include <assert.h> | |
+#include <errno.h> | |
+#include <stdbool.h> | |
+#include <stdio.h> | |
+#include <stdlib.h> | |
+#include <string.h> | |
+ | |
+#include "message.h" | |
+#include "xgettext.h" | |
+#include "error.h" | |
+#include "error-progname.h" | |
+#include "progname.h" | |
+#include "basename.h" | |
+#include "xerror.h" | |
+#include "xvasprintf.h" | |
+#include "xalloc.h" | |
+#include "c-strstr.h" | |
+#include "c-ctype.h" | |
+#include "po-charset.h" | |
+#include "uniname.h" | |
+#include "unistr.h" | |
+#include "gettext.h" | |
+ | |
+#define _(s) gettext(s) | |
+ | |
+#define max(a,b) ((a) > (b) ? (a) : (b)) | |
+ | |
+#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) | |
+ | |
+/* The JavaScript aka ECMA-Script syntax is defined in ECMA-262 | |
+ specification: | |
+ http://www.ecma-international.org/publications/standards/Ecma-262.htm */ | |
+ | |
+/* ====================== Keyword set customization. ====================== */ | |
+ | |
+/* If true extract all strings. */ | |
+static bool extract_all = false; | |
+ | |
+static hash_table keywords; | |
+static bool default_keywords = true; | |
+ | |
+ | |
+void | |
+x_javascript_extract_all () | |
+{ | |
+ extract_all = true; | |
+} | |
+ | |
+ | |
+void | |
+x_javascript_keyword (const char *name) | |
+{ | |
+ if (name == NULL) | |
+ default_keywords = false; | |
+ else | |
+ { | |
+ const char *end; | |
+ struct callshape shape; | |
+ const char *colon; | |
+ | |
+ if (keywords.table == NULL) | |
+ hash_init (&keywords, 100); | |
+ | |
+ split_keywordspec (name, &end, &shape); | |
+ | |
+ /* The characters between name and end should form a valid C identifier. | |
+ A colon means an invalid parse in split_keywordspec(). */ | |
+ colon = strchr (name, ':'); | |
+ if (colon == NULL || colon >= end) | |
+ insert_keyword_callshape (&keywords, name, end - name, &shape); | |
+ } | |
+} | |
+ | |
+/* Finish initializing the keywords hash table. | |
+ Called after argument processing, before each file is processed. */ | |
+static void | |
+init_keywords () | |
+{ | |
+ if (default_keywords) | |
+ { | |
+ /* When adding new keywords here, also update the documentation in | |
+ xgettext.texi! */ | |
+ x_javascript_keyword ("gettext"); | |
+ x_javascript_keyword ("ugettext"); | |
+ x_javascript_keyword ("dgettext:2"); | |
+ x_javascript_keyword ("ngettext:1,2"); | |
+ x_javascript_keyword ("ungettext:1,2"); | |
+ x_javascript_keyword ("dngettext:2,3"); | |
+ x_javascript_keyword ("_"); | |
+ default_keywords = false; | |
+ } | |
+} | |
+ | |
+void | |
+init_flag_table_javascript () | |
+{ | |
+ xgettext_record_flag ("gettext:1:pass-javascript-format"); | |
+ xgettext_record_flag ("ugettext:1:pass-javascript-format"); | |
+ xgettext_record_flag ("dgettext:2:pass-javascript-format"); | |
+ xgettext_record_flag ("ngettext:1:pass-javascript-format"); | |
+ xgettext_record_flag ("ngettext:2:pass-javascript-format"); | |
+ xgettext_record_flag ("ungettext:1:pass-javascript-format"); | |
+ xgettext_record_flag ("ungettext:2:pass-javascript-format"); | |
+ xgettext_record_flag ("dngettext:2:pass-javascript-format"); | |
+ xgettext_record_flag ("dngettext:3:pass-javascript-format"); | |
+ xgettext_record_flag ("_:1:pass-javascript-format"); | |
+} | |
+ | |
+ | |
+/* ======================== Reading of characters. ======================== */ | |
+ | |
+/* Real filename, used in error messages about the input file. */ | |
+static const char *real_file_name; | |
+ | |
+/* Logical filename and line number, used to label the extracted messages. */ | |
+static char *logical_file_name; | |
+static int line_number; | |
+ | |
+/* The input file stream. */ | |
+static FILE *fp; | |
+ | |
+ | |
+/* 1. line_number handling. */ | |
+ | |
+/* Maximum used, roughly a safer MB_LEN_MAX. */ | |
+#define MAX_PHASE1_PUSHBACK 16 | |
+static unsigned char phase1_pushback[MAX_PHASE1_PUSHBACK]; | |
+static int phase1_pushback_length; | |
+ | |
+/* Read the next single byte from the input file. */ | |
+static int | |
+phase1_getc () | |
+{ | |
+ int c; | |
+ | |
+ if (phase1_pushback_length) | |
+ c = phase1_pushback[--phase1_pushback_length]; | |
+ else | |
+ { | |
+ c = getc (fp); | |
+ | |
+ if (c == EOF) | |
+ { | |
+ if (ferror (fp)) | |
+ error (EXIT_FAILURE, errno, _("error while reading \"%s\""), | |
+ real_file_name); | |
+ return EOF; | |
+ } | |
+ } | |
+ | |
+ if (c == '\n') | |
+ ++line_number; | |
+ | |
+ return c; | |
+} | |
+ | |
+/* Supports MAX_PHASE1_PUSHBACK characters of pushback. */ | |
+static void | |
+phase1_ungetc (int c) | |
+{ | |
+ if (c != EOF) | |
+ { | |
+ if (c == '\n') | |
+ --line_number; | |
+ | |
+ if (phase1_pushback_length == SIZEOF (phase1_pushback)) | |
+ abort (); | |
+ phase1_pushback[phase1_pushback_length++] = c; | |
+ } | |
+} | |
+ | |
+ | |
+/* Phase 2: Conversion to Unicode. | |
+ For now, we expect Javascript files to be encoded as UTF-8 */ | |
+ | |
+/* End-of-file indicator for functions returning an UCS-4 character. */ | |
+#define UEOF -1 | |
+ | |
+static lexical_context_ty lexical_context; | |
+ | |
+static int phase2_pushback[max (9, UNINAME_MAX + 3)]; | |
+static int phase2_pushback_length; | |
+ | |
+/* Read the next Unicode UCS-4 character from the input file. */ | |
+static int | |
+phase2_getc () | |
+{ | |
+ if (phase2_pushback_length) | |
+ return phase2_pushback[--phase2_pushback_length]; | |
+ | |
+ if (xgettext_current_source_encoding == po_charset_ascii) | |
+ { | |
+ int c = phase1_getc (); | |
+ if (c == EOF) | |
+ return UEOF; | |
+ if (!c_isascii (c)) | |
+ { | |
+ multiline_error (xstrdup (""), | |
+ xasprintf ("%s\n%s\n", | |
+ non_ascii_error_message (lexical_context, | |
+ real_file_name, | |
+ line_number), | |
+ _("\ | |
+Please specify the source encoding through --from-code\n"))); | |
+ exit (EXIT_FAILURE); | |
+ } | |
+ return c; | |
+ } | |
+ else if (xgettext_current_source_encoding != po_charset_utf8) | |
+ { | |
+#if HAVE_ICONV | |
+ /* Use iconv on an increasing number of bytes. Read only as many bytes | |
+ through phase1_getc as needed. This is needed to give reasonable | |
+ interactive behaviour when fp is connected to an interactive tty. */ | |
+ unsigned char buf[MAX_PHASE1_PUSHBACK]; | |
+ size_t bufcount; | |
+ int c = phase1_getc (); | |
+ if (c == EOF) | |
+ return UEOF; | |
+ buf[0] = (unsigned char) c; | |
+ bufcount = 1; | |
+ | |
+ for (;;) | |
+ { | |
+ unsigned char scratchbuf[6]; | |
+ const char *inptr = (const char *) &buf[0]; | |
+ size_t insize = bufcount; | |
+ char *outptr = (char *) &scratchbuf[0]; | |
+ size_t outsize = sizeof (scratchbuf); | |
+ | |
+ size_t res = iconv (xgettext_current_source_iconv, | |
+ (ICONV_CONST char **) &inptr, &insize, | |
+ &outptr, &outsize); | |
+ /* We expect that a character has been produced if and only if | |
+ some input bytes have been consumed. */ | |
+ if ((insize < bufcount) != (outsize < sizeof (scratchbuf))) | |
+ abort (); | |
+ if (outsize == sizeof (scratchbuf)) | |
+ { | |
+ /* No character has been produced. Must be an error. */ | |
+ if (res != (size_t)(-1)) | |
+ abort (); | |
+ | |
+ if (errno == EILSEQ) | |
+ { | |
+ /* An invalid multibyte sequence was encountered. */ | |
+ multiline_error (xstrdup (""), | |
+ xasprintf (_("\ | |
+%s:%d: Invalid multibyte sequence.\n\ | |
+Please specify the correct source encoding through --from-code\n"), | |
+ real_file_name, line_number)); | |
+ exit (EXIT_FAILURE); | |
+ } | |
+ else if (errno == EINVAL) | |
+ { | |
+ /* An incomplete multibyte character. */ | |
+ int c; | |
+ | |
+ if (bufcount == MAX_PHASE1_PUSHBACK) | |
+ { | |
+ /* An overlong incomplete multibyte sequence was | |
+ encountered. */ | |
+ multiline_error (xstrdup (""), | |
+ xasprintf (_("\ | |
+%s:%d: Long incomplete multibyte sequence.\n\ | |
+Please specify the correct source encoding through --from-code\n"), | |
+ real_file_name, line_number)); | |
+ exit (EXIT_FAILURE); | |
+ } | |
+ | |
+ /* Read one more byte and retry iconv. */ | |
+ c = phase1_getc (); | |
+ if (c == EOF) | |
+ { | |
+ multiline_error (xstrdup (""), | |
+ xasprintf (_("\ | |
+%s:%d: Incomplete multibyte sequence at end of file.\n\ | |
+Please specify the correct source encoding through --from-code\n"), | |
+ real_file_name, line_number)); | |
+ exit (EXIT_FAILURE); | |
+ } | |
+ if (c == '\n') | |
+ { | |
+ multiline_error (xstrdup (""), | |
+ xasprintf (_("\ | |
+%s:%d: Incomplete multibyte sequence at end of line.\n\ | |
+Please specify the correct source encoding through --from-code\n"), | |
+ real_file_name, line_number - 1)); | |
+ exit (EXIT_FAILURE); | |
+ } | |
+ buf[bufcount++] = (unsigned char) c; | |
+ } | |
+ else | |
+ error (EXIT_FAILURE, errno, _("%s:%d: iconv failure"), | |
+ real_file_name, line_number); | |
+ } | |
+ else | |
+ { | |
+ size_t outbytes = sizeof (scratchbuf) - outsize; | |
+ size_t bytes = bufcount - insize; | |
+ ucs4_t uc; | |
+ | |
+ /* We expect that one character has been produced. */ | |
+ if (bytes == 0) | |
+ abort (); | |
+ if (outbytes == 0) | |
+ abort (); | |
+ /* Push back the unused bytes. */ | |
+ while (insize > 0) | |
+ phase1_ungetc (buf[--insize]); | |
+ /* Convert the character from UTF-8 to UCS-4. */ | |
+ if (u8_mbtouc (&uc, scratchbuf, outbytes) < outbytes) | |
+ { | |
+ /* scratchbuf contains an out-of-range Unicode character | |
+ (> 0x10ffff). */ | |
+ multiline_error (xstrdup (""), | |
+ xasprintf (_("\ | |
+%s:%d: Invalid multibyte sequence.\n\ | |
+Please specify the source encoding through --from-code\n"), | |
+ real_file_name, line_number)); | |
+ exit (EXIT_FAILURE); | |
+ } | |
+ return uc; | |
+ } | |
+ } | |
+#else | |
+ /* If we don't have iconv(), the only supported values for | |
+ xgettext_global_source_encoding and thus also for | |
+ xgettext_current_source_encoding are ASCII and UTF-8. */ | |
+ abort (); | |
+#endif | |
+ } | |
+ else | |
+ { | |
+ /* Read an UTF-8 encoded character. */ | |
+ unsigned char buf[6]; | |
+ unsigned int count; | |
+ int c; | |
+ ucs4_t uc; | |
+ | |
+ c = phase1_getc (); | |
+ if (c == EOF) | |
+ return UEOF; | |
+ buf[0] = c; | |
+ count = 1; | |
+ | |
+ if (buf[0] >= 0xc0) | |
+ { | |
+ c = phase1_getc (); | |
+ if (c == EOF) | |
+ return UEOF; | |
+ buf[1] = c; | |
+ count = 2; | |
+ } | |
+ | |
+ if (buf[0] >= 0xe0 | |
+ && ((buf[1] ^ 0x80) < 0x40)) | |
+ { | |
+ c = phase1_getc (); | |
+ if (c == EOF) | |
+ return UEOF; | |
+ buf[2] = c; | |
+ count = 3; | |
+ } | |
+ | |
+ if (buf[0] >= 0xf0 | |
+ && ((buf[1] ^ 0x80) < 0x40) | |
+ && ((buf[2] ^ 0x80) < 0x40)) | |
+ { | |
+ c = phase1_getc (); | |
+ if (c == EOF) | |
+ return UEOF; | |
+ buf[3] = c; | |
+ count = 4; | |
+ } | |
+ | |
+ if (buf[0] >= 0xf8 | |
+ && ((buf[1] ^ 0x80) < 0x40) | |
+ && ((buf[2] ^ 0x80) < 0x40) | |
+ && ((buf[3] ^ 0x80) < 0x40)) | |
+ { | |
+ c = phase1_getc (); | |
+ if (c == EOF) | |
+ return UEOF; | |
+ buf[4] = c; | |
+ count = 5; | |
+ } | |
+ | |
+ if (buf[0] >= 0xfc | |
+ && ((buf[1] ^ 0x80) < 0x40) | |
+ && ((buf[2] ^ 0x80) < 0x40) | |
+ && ((buf[3] ^ 0x80) < 0x40) | |
+ && ((buf[4] ^ 0x80) < 0x40)) | |
+ { | |
+ c = phase1_getc (); | |
+ if (c == EOF) | |
+ return UEOF; | |
+ buf[5] = c; | |
+ count = 6; | |
+ } | |
+ | |
+ u8_mbtouc (&uc, buf, count); | |
+ return uc; | |
+ } | |
+} | |
+ | |
+/* Supports max (9, UNINAME_MAX + 3) pushback characters. */ | |
+static void | |
+phase2_ungetc (int c) | |
+{ | |
+ if (c != UEOF) | |
+ { | |
+ if (phase2_pushback_length == SIZEOF (phase2_pushback)) | |
+ abort (); | |
+ phase2_pushback[phase2_pushback_length++] = c; | |
+ } | |
+} | |
+ | |
+ | |
+/* ========================= Accumulating strings. ======================== */ | |
+ | |
+/* A string buffer type that allows appending Unicode characters. | |
+ Returns the entire string in UTF-8 encoding. */ | |
+ | |
+struct unicode_string_buffer | |
+{ | |
+ /* The part of the string that has already been converted to UTF-8. */ | |
+ char *utf8_buffer; | |
+ size_t utf8_buflen; | |
+ size_t utf8_allocated; | |
+}; | |
+ | |
+/* Initialize a 'struct unicode_string_buffer' to empty. */ | |
+static inline void | |
+init_unicode_string_buffer (struct unicode_string_buffer *bp) | |
+{ | |
+ bp->utf8_buffer = NULL; | |
+ bp->utf8_buflen = 0; | |
+ bp->utf8_allocated = 0; | |
+} | |
+ | |
+/* Auxiliary function: Ensure count more bytes are available in bp->utf8. */ | |
+static inline void | |
+unicode_string_buffer_append_unicode_grow (struct unicode_string_buffer *bp, | |
+ size_t count) | |
+{ | |
+ if (bp->utf8_buflen + count > bp->utf8_allocated) | |
+ { | |
+ size_t new_allocated = 2 * bp->utf8_allocated + 10; | |
+ if (new_allocated < bp->utf8_buflen + count) | |
+ new_allocated = bp->utf8_buflen + count; | |
+ bp->utf8_allocated = new_allocated; | |
+ bp->utf8_buffer = xrealloc (bp->utf8_buffer, new_allocated); | |
+ } | |
+} | |
+ | |
+/* Auxiliary function: Append a Unicode character to bp->utf8. | |
+ uc must be < 0x110000. */ | |
+static inline void | |
+unicode_string_buffer_append_unicode (struct unicode_string_buffer *bp, | |
+ unsigned int uc) | |
+{ | |
+ unsigned char utf8buf[6]; | |
+ int count = u8_uctomb (utf8buf, uc, 6); | |
+ | |
+ if (count < 0) | |
+ /* The caller should have ensured that uc is not out-of-range. */ | |
+ abort (); | |
+ | |
+ unicode_string_buffer_append_unicode_grow (bp, count); | |
+ memcpy (bp->utf8_buffer + bp->utf8_buflen, utf8buf, count); | |
+ bp->utf8_buflen += count; | |
+} | |
+ | |
+/* Return the string buffer's contents. */ | |
+static char * | |
+unicode_string_buffer_result (struct unicode_string_buffer *bp) | |
+{ | |
+ /* NUL-terminate it. */ | |
+ unicode_string_buffer_append_unicode_grow (bp, 1); | |
+ bp->utf8_buffer[bp->utf8_buflen] = '\0'; | |
+ /* Return it. */ | |
+ return bp->utf8_buffer; | |
+} | |
+ | |
+/* Free the memory pointed to by a 'struct unicode_string_buffer'. */ | |
+static inline void | |
+free_unicode_string_buffer (struct unicode_string_buffer *bp) | |
+{ | |
+ free (bp->utf8_buffer); | |
+} | |
+ | |
+ | |
+/* ======================== Accumulating comments. ======================== */ | |
+ | |
+ | |
+/* Accumulating a single comment line. */ | |
+ | |
+static struct unicode_string_buffer comment_buffer; | |
+ | |
+static inline void | |
+comment_start () | |
+{ | |
+ lexical_context = lc_comment; | |
+ comment_buffer.utf8_buflen = 0; | |
+} | |
+ | |
+static inline bool | |
+comment_at_start () | |
+{ | |
+ return (comment_buffer.utf8_buflen == 0); | |
+} | |
+ | |
+static inline void | |
+comment_add (int c) | |
+{ | |
+ unicode_string_buffer_append_unicode (&comment_buffer, c); | |
+} | |
+ | |
+static inline const char * | |
+comment_line_end () | |
+{ | |
+ char *buffer = unicode_string_buffer_result (&comment_buffer); | |
+ size_t buflen = strlen (buffer); | |
+ | |
+ while (buflen >= 1 | |
+ && (buffer[buflen - 1] == ' ' || buffer[buflen - 1] == '\t')) | |
+ --buflen; | |
+ buffer[buflen] = '\0'; | |
+ savable_comment_add (buffer); | |
+ lexical_context = lc_outside; | |
+ return buffer; | |
+} | |
+ | |
+ | |
+/* These are for tracking whether comments count as immediately before | |
+ keyword. */ | |
+static int last_comment_line; | |
+static int last_non_comment_line; | |
+ | |
+ | |
+/* ======================== Recognizing comments. ======================== */ | |
+ | |
+ | |
+/* Recognizing the "coding" comment. | |
+ JavaScript provides to different comments: C-Style or C++ style | |
+*/ | |
+ | |
+/* Canonicalized encoding name for the current input file. */ | |
+static const char *xgettext_current_file_source_encoding; | |
+ | |
+#if HAVE_ICONV | |
+/* Converter from xgettext_current_file_source_encoding to UTF-8 (except from | |
+ ASCII or UTF-8, when this conversion is a no-op). */ | |
+static iconv_t xgettext_current_file_source_iconv; | |
+#endif | |
+ | |
+/* Tracking whether the current line is a continuation line or contains a | |
+ non-blank character. */ | |
+static bool continuation_or_nonblank_line = false; | |
+ | |
+/* Phase 3: Outside strings, replace backslash-newline with nothing and a | |
+ comment with nothing. */ | |
+ | |
+static int | |
+phase3_getc () | |
+{ | |
+ int c; | |
+ | |
+ for (;;) | |
+ { | |
+ c = phase2_getc (); | |
+ if (c == '\\') | |
+ { | |
+ c = phase2_getc (); | |
+ if (c != '\n') | |
+ { | |
+ phase2_ungetc (c); | |
+ /* This shouldn't happen usually, because "A backslash is | |
+ illegal elsewhere on a line outside a string literal." */ | |
+ return '\\'; | |
+ } | |
+ /* Eat backslash-newline. */ | |
+ continuation_or_nonblank_line = true; | |
+ } | |
+ else if (c == '/') | |
+ { | |
+ c = phase2_getc (); | |
+ if (c == '/') | |
+ { | |
+ /* Eat a comment to the end of line. */ | |
+ last_comment_line = line_number; | |
+ comment_start (); | |
+ for (;;) | |
+ { | |
+ c = phase2_getc (); | |
+ if (c == UEOF || c == '\n') | |
+ break; | |
+ /* We skip all leading white space, but not EOLs. */ | |
+ if (!(comment_at_start () && (c == ' ' || c == '\t'))) | |
+ comment_add (c); | |
+ } | |
+ continuation_or_nonblank_line = false; | |
+ return c; | |
+ } | |
+ else if (c == '*') | |
+ { | |
+ /* Eat a comment to the end marker. (like this one!) */ | |
+ last_comment_line = line_number; | |
+ comment_start (); | |
+ for (;;) | |
+ { | |
+ c = phase2_getc (); | |
+ if (c == UEOF) | |
+ break; | |
+ if (c == '*') | |
+ { | |
+ c = phase2_getc (); | |
+ if (c == '/') | |
+ break; | |
+ else | |
+ phase2_ungetc (c); | |
+ } | |
+ comment_add (c); | |
+ } | |
+ continuation_or_nonblank_line = false; | |
+ } | |
+ else | |
+ { | |
+ phase2_ungetc (c); | |
+ return '/'; | |
+ } | |
+ } | |
+ else | |
+ { | |
+ if (c == '\n') | |
+ continuation_or_nonblank_line = false; | |
+ else if (!(c == ' ' || c == '\t' || c == '\f')) | |
+ continuation_or_nonblank_line = true; | |
+ return c; | |
+ } | |
+ } | |
+} | |
+ | |
+/* Supports only one pushback character. */ | |
+static void | |
+phase3_ungetc (int c) | |
+{ | |
+ phase2_ungetc (c); | |
+} | |
+ | |
+ | |
+/* ========================= Accumulating strings. ======================== */ | |
+ | |
+/* Return value of phase7_getuc when EOF is reached. */ | |
+#define P7_EOF (-1) | |
+#define P7_STRING_END (-2) | |
+ | |
+/* Convert an UTF-16 or UTF-32 code point to a return value that can be | |
+ distinguished from a single-byte return value. */ | |
+#define UNICODE(code) (0x100 + (code)) | |
+ | |
+/* Test a return value of phase7_getuc whether it designates an UTF-16 or | |
+ UTF-32 code point. */ | |
+#define IS_UNICODE(p7_result) ((p7_result) >= 0x100) | |
+ | |
+/* Extract the UTF-16 or UTF-32 code of a return value that satisfies | |
+ IS_UNICODE. */ | |
+#define UNICODE_VALUE(p7_result) ((p7_result) - 0x100) | |
+ | |
+/* A string buffer type that allows appending bytes (in the | |
+ xgettext_current_source_encoding) or Unicode characters. | |
+ Returns the entire string in UTF-8 encoding. */ | |
+ | |
+struct mixed_string_buffer | |
+{ | |
+ /* The part of the string that has already been converted to UTF-8. */ | |
+ char *utf8_buffer; | |
+ size_t utf8_buflen; | |
+ size_t utf8_allocated; | |
+ /* The first half of an UTF-16 surrogate character. */ | |
+ unsigned short utf16_surr; | |
+ /* The part of the string that is still in the source encoding. */ | |
+ char *curr_buffer; | |
+ size_t curr_buflen; | |
+ size_t curr_allocated; | |
+ /* The lexical context. Used only for error message purposes. */ | |
+ lexical_context_ty lcontext; | |
+}; | |
+ | |
+/* Initialize a 'struct mixed_string_buffer' to empty. */ | |
+static inline void | |
+init_mixed_string_buffer (struct mixed_string_buffer *bp, lexical_context_ty lcontext) | |
+{ | |
+ bp->utf8_buffer = NULL; | |
+ bp->utf8_buflen = 0; | |
+ bp->utf8_allocated = 0; | |
+ bp->utf16_surr = 0; | |
+ bp->curr_buffer = NULL; | |
+ bp->curr_buflen = 0; | |
+ bp->curr_allocated = 0; | |
+ bp->lcontext = lcontext; | |
+} | |
+ | |
+/* Auxiliary function: Append a byte to bp->curr. */ | |
+static inline void | |
+mixed_string_buffer_append_byte (struct mixed_string_buffer *bp, unsigned char c) | |
+{ | |
+ if (bp->curr_buflen == bp->curr_allocated) | |
+ { | |
+ bp->curr_allocated = 2 * bp->curr_allocated + 10; | |
+ bp->curr_buffer = xrealloc (bp->curr_buffer, bp->curr_allocated); | |
+ } | |
+ bp->curr_buffer[bp->curr_buflen++] = c; | |
+} | |
+ | |
+/* Auxiliary function: Ensure count more bytes are available in bp->utf8. */ | |
+static inline void | |
+mixed_string_buffer_append_unicode_grow (struct mixed_string_buffer *bp, size_t count) | |
+{ | |
+ if (bp->utf8_buflen + count > bp->utf8_allocated) | |
+ { | |
+ size_t new_allocated = 2 * bp->utf8_allocated + 10; | |
+ if (new_allocated < bp->utf8_buflen + count) | |
+ new_allocated = bp->utf8_buflen + count; | |
+ bp->utf8_allocated = new_allocated; | |
+ bp->utf8_buffer = xrealloc (bp->utf8_buffer, new_allocated); | |
+ } | |
+} | |
+ | |
+/* Auxiliary function: Append a Unicode character to bp->utf8. | |
+ uc must be < 0x110000. */ | |
+static inline void | |
+mixed_string_buffer_append_unicode (struct mixed_string_buffer *bp, ucs4_t uc) | |
+{ | |
+ unsigned char utf8buf[6]; | |
+ int count = u8_uctomb (utf8buf, uc, 6); | |
+ | |
+ if (count < 0) | |
+ /* The caller should have ensured that uc is not out-of-range. */ | |
+ abort (); | |
+ | |
+ mixed_string_buffer_append_unicode_grow (bp, count); | |
+ memcpy (bp->utf8_buffer + bp->utf8_buflen, utf8buf, count); | |
+ bp->utf8_buflen += count; | |
+} | |
+ | |
+/* Auxiliary function: Flush bp->utf16_surr into bp->utf8_buffer. */ | |
+static inline void | |
+mixed_string_buffer_flush_utf16_surr (struct mixed_string_buffer *bp) | |
+{ | |
+ if (bp->utf16_surr != 0) | |
+ { | |
+ /* A half surrogate is invalid, therefore use U+FFFD instead. */ | |
+ mixed_string_buffer_append_unicode (bp, 0xfffd); | |
+ bp->utf16_surr = 0; | |
+ } | |
+} | |
+ | |
+/* Auxiliary function: Flush bp->curr_buffer into bp->utf8_buffer. */ | |
+static inline void | |
+mixed_string_buffer_flush_curr_buffer (struct mixed_string_buffer *bp, int lineno) | |
+{ | |
+ if (bp->curr_buflen > 0) | |
+ { | |
+ char *curr; | |
+ size_t count; | |
+ | |
+ mixed_string_buffer_append_byte (bp, '\0'); | |
+ | |
+ /* Convert from the source encoding to UTF-8. */ | |
+ curr = from_current_source_encoding (bp->curr_buffer, bp->lcontext, | |
+ logical_file_name, lineno); | |
+ | |
+ /* Append it to bp->utf8_buffer. */ | |
+ count = strlen (curr); | |
+ mixed_string_buffer_append_unicode_grow (bp, count); | |
+ memcpy (bp->utf8_buffer + bp->utf8_buflen, curr, count); | |
+ bp->utf8_buflen += count; | |
+ | |
+ if (curr != bp->curr_buffer) | |
+ free (curr); | |
+ bp->curr_buflen = 0; | |
+ } | |
+} | |
+ | |
+/* Append a character or Unicode character to a 'struct mixed_string_buffer'. */ | |
+static void | |
+mixed_string_buffer_append (struct mixed_string_buffer *bp, int c) | |
+{ | |
+ if (IS_UNICODE (c)) | |
+ { | |
+ /* Append a Unicode character. */ | |
+ | |
+ /* Switch from multibyte character mode to Unicode character mode. */ | |
+ mixed_string_buffer_flush_curr_buffer (bp, line_number); | |
+ | |
+ /* Test whether this character and the previous one form a Unicode | |
+ surrogate character pair. */ | |
+ if (bp->utf16_surr != 0 | |
+ && (c >= UNICODE (0xdc00) && c < UNICODE (0xe000))) | |
+ { | |
+ unsigned short utf16buf[2]; | |
+ ucs4_t uc; | |
+ | |
+ utf16buf[0] = bp->utf16_surr; | |
+ utf16buf[1] = UNICODE_VALUE (c); | |
+ if (u16_mbtouc (&uc, utf16buf, 2) != 2) | |
+ abort (); | |
+ | |
+ mixed_string_buffer_append_unicode (bp, uc); | |
+ bp->utf16_surr = 0; | |
+ } | |
+ else | |
+ { | |
+ mixed_string_buffer_flush_utf16_surr (bp); | |
+ | |
+ if (c >= UNICODE (0xd800) && c < UNICODE (0xdc00)) | |
+ bp->utf16_surr = UNICODE_VALUE (c); | |
+ else if (c >= UNICODE (0xdc00) && c < UNICODE (0xe000)) | |
+ { | |
+ /* A half surrogate is invalid, therefore use U+FFFD instead. */ | |
+ mixed_string_buffer_append_unicode (bp, 0xfffd); | |
+ } | |
+ else | |
+ mixed_string_buffer_append_unicode (bp, UNICODE_VALUE (c)); | |
+ } | |
+ } | |
+ else | |
+ { | |
+ /* Append a single byte. */ | |
+ | |
+ /* Switch from Unicode character mode to multibyte character mode. */ | |
+ mixed_string_buffer_flush_utf16_surr (bp); | |
+ | |
+ /* When a newline is seen, convert the accumulated multibyte sequence. | |
+ This ensures a correct line number in the error message in case of | |
+ a conversion error. The "- 1" is to account for the newline. */ | |
+ if (c == '\n') | |
+ mixed_string_buffer_flush_curr_buffer (bp, line_number - 1); | |
+ | |
+ mixed_string_buffer_append_byte (bp, (unsigned char) c); | |
+ } | |
+} | |
+ | |
+/* Return the string buffer's contents. */ | |
+static char * | |
+mixed_string_buffer_result (struct mixed_string_buffer *bp) | |
+{ | |
+ /* Flush all into bp->utf8_buffer. */ | |
+ mixed_string_buffer_flush_utf16_surr (bp); | |
+ mixed_string_buffer_flush_curr_buffer (bp, line_number); | |
+ /* NUL-terminate it. */ | |
+ mixed_string_buffer_append_unicode_grow (bp, 1); | |
+ bp->utf8_buffer[bp->utf8_buflen] = '\0'; | |
+ /* Return it. */ | |
+ return bp->utf8_buffer; | |
+} | |
+ | |
+/* Free the memory pointed to by a 'struct mixed_string_buffer'. */ | |
+static inline void | |
+free_mixed_string_buffer (struct mixed_string_buffer *bp) | |
+{ | |
+ free (bp->utf8_buffer); | |
+ free (bp->curr_buffer); | |
+} | |
+ | |
+ | |
+/* ========================== Reading of tokens. ========================== */ | |
+ | |
+ | |
+enum token_type_ty | |
+{ | |
+ token_type_eof, | |
+ token_type_lparen, /* ( */ | |
+ token_type_rparen, /* ) */ | |
+ token_type_comma, /* , */ | |
+ token_type_lbracket, /* [ */ | |
+ token_type_rbracket, /* ] */ | |
+ token_type_regexp, /* /.../ */ | |
+ token_type_operator, /* + - * / % . < > = ~ ! | & ? : ^ */ | |
+ token_type_string, /* "abc", 'abc' */ | |
+ token_type_symbol, /* symbol, number */ | |
+ token_type_other /* misc. operator */ | |
+}; | |
+typedef enum token_type_ty token_type_ty; | |
+ | |
+typedef struct token_ty token_ty; | |
+struct token_ty | |
+{ | |
+ token_type_ty type; | |
+ char *string; /* for token_type_string, token_type_symbol */ | |
+ refcounted_string_list_ty *comment; /* for token_type_string */ | |
+ int line_number; | |
+ int operator; /* for token_type_operator */ | |
+}; | |
+ | |
+/* Javascript provides strings with either double or single quotes. | |
+ "abc" or 'abc' | |
+ Both may contain special sequences after backslash: | |
+ \\, \", \', \a\b\f\n\r\t\v | |
+ Special characters from ascii range are selected by on of the following | |
+ octal or hexadecimal sequences: | |
+ \oOO \xXX | |
+ Any unicode point can be entered using the sequence \uNNNN or \uNNNNNNNN | |
+ */ | |
+ | |
+static int | |
+phase7_getuc (int quote_char, | |
+ bool interpret_ansic, bool interpret_unicode, | |
+ unsigned int *backslash_counter) | |
+{ | |
+ int c; | |
+ | |
+ for (;;) | |
+ { | |
+ /* Use phase 2, because phase 3 elides comments. */ | |
+ c = phase2_getc (); | |
+ | |
+ if (c == UEOF) | |
+ return P7_EOF; | |
+ | |
+ if (c == quote_char && (interpret_ansic || (*backslash_counter & 1) == 0)) | |
+ { | |
+ return P7_STRING_END; | |
+ } | |
+ | |
+ if (c == '\n') | |
+ { | |
+ phase2_ungetc (c); | |
+ error_with_progname = false; | |
+ error (0, 0, _("%s:%d: warning: unterminated string"), | |
+ logical_file_name, line_number); | |
+ error_with_progname = true; | |
+ return P7_STRING_END; | |
+ } | |
+ | |
+ if (c != '\\') | |
+ { | |
+ *backslash_counter = 0; | |
+ return UNICODE (c); | |
+ } | |
+ | |
+ /* Backslash handling. */ | |
+ | |
+ if (!interpret_ansic && !interpret_unicode) | |
+ { | |
+ ++*backslash_counter; | |
+ return UNICODE ('\\'); | |
+ } | |
+ | |
+ /* Dispatch according to the character following the backslash. */ | |
+ c = phase2_getc (); | |
+ if (c == UEOF) | |
+ { | |
+ ++*backslash_counter; | |
+ return UNICODE ('\\'); | |
+ } | |
+ | |
+ if (interpret_ansic) | |
+ switch (c) | |
+ { | |
+ case '\n': | |
+ continue; | |
+ case '\\': | |
+ ++*backslash_counter; | |
+ return UNICODE (c); | |
+ case '\'': case '"': | |
+ *backslash_counter = 0; | |
+ return UNICODE (c); | |
+ case 'a': | |
+ *backslash_counter = 0; | |
+ return UNICODE ('\a'); | |
+ case 'b': | |
+ *backslash_counter = 0; | |
+ return UNICODE ('\b'); | |
+ case 'f': | |
+ *backslash_counter = 0; | |
+ return UNICODE ('\f'); | |
+ case 'n': | |
+ *backslash_counter = 0; | |
+ return UNICODE ('\n'); | |
+ case 'r': | |
+ *backslash_counter = 0; | |
+ return UNICODE ('\r'); | |
+ case 't': | |
+ *backslash_counter = 0; | |
+ return UNICODE ('\t'); | |
+ case 'v': | |
+ *backslash_counter = 0; | |
+ return UNICODE ('\v'); | |
+ case '0': case '1': case '2': case '3': case '4': | |
+ case '5': case '6': case '7': | |
+ { | |
+ int n = c - '0'; | |
+ | |
+ c = phase2_getc (); | |
+ if (c != UEOF) | |
+ { | |
+ if (c >= '0' && c <= '7') | |
+ { | |
+ n = (n << 3) + (c - '0'); | |
+ c = phase2_getc (); | |
+ if (c != UEOF) | |
+ { | |
+ if (c >= '0' && c <= '7') | |
+ n = (n << 3) + (c - '0'); | |
+ else | |
+ phase2_ungetc (c); | |
+ } | |
+ } | |
+ else | |
+ phase2_ungetc (c); | |
+ } | |
+ *backslash_counter = 0; | |
+ if (interpret_unicode) | |
+ return UNICODE (n); | |
+ else | |
+ return (unsigned char) n; | |
+ } | |
+ case 'x': | |
+ { | |
+ int c1 = phase2_getc (); | |
+ int n1; | |
+ | |
+ if (c1 >= '0' && c1 <= '9') | |
+ n1 = c1 - '0'; | |
+ else if (c1 >= 'A' && c1 <= 'F') | |
+ n1 = c1 - 'A' + 10; | |
+ else if (c1 >= 'a' && c1 <= 'f') | |
+ n1 = c1 - 'a' + 10; | |
+ else | |
+ n1 = -1; | |
+ | |
+ if (n1 >= 0) | |
+ { | |
+ int c2 = phase2_getc (); | |
+ int n2; | |
+ | |
+ if (c2 >= '0' && c2 <= '9') | |
+ n2 = c2 - '0'; | |
+ else if (c2 >= 'A' && c2 <= 'F') | |
+ n2 = c2 - 'A' + 10; | |
+ else if (c2 >= 'a' && c2 <= 'f') | |
+ n2 = c2 - 'a' + 10; | |
+ else | |
+ n2 = -1; | |
+ | |
+ if (n2 >= 0) | |
+ { | |
+ int n = (n1 << 4) + n2; | |
+ *backslash_counter = 0; | |
+ if (interpret_unicode) | |
+ return UNICODE (n); | |
+ else | |
+ return (unsigned char) n; | |
+ } | |
+ | |
+ phase2_ungetc (c2); | |
+ } | |
+ phase2_ungetc (c1); | |
+ phase2_ungetc (c); | |
+ ++*backslash_counter; | |
+ return UNICODE ('\\'); | |
+ } | |
+ } | |
+ | |
+ if (interpret_unicode) | |
+ { | |
+ if (c == 'u') | |
+ { | |
+ unsigned char buf[4]; | |
+ unsigned int n = 0; | |
+ int i; | |
+ | |
+ for (i = 0; i < 4; i++) | |
+ { | |
+ int c1 = phase2_getc (); | |
+ | |
+ if (c1 >= '0' && c1 <= '9') | |
+ n = (n << 4) + (c1 - '0'); | |
+ else if (c1 >= 'A' && c1 <= 'F') | |
+ n = (n << 4) + (c1 - 'A' + 10); | |
+ else if (c1 >= 'a' && c1 <= 'f') | |
+ n = (n << 4) + (c1 - 'a' + 10); | |
+ else | |
+ { | |
+ phase2_ungetc (c1); | |
+ while (--i >= 0) | |
+ phase2_ungetc (buf[i]); | |
+ phase2_ungetc (c); | |
+ ++*backslash_counter; | |
+ return UNICODE ('\\'); | |
+ } | |
+ | |
+ buf[i] = c1; | |
+ } | |
+ *backslash_counter = 0; | |
+ return UNICODE (n); | |
+ } | |
+ | |
+ if (interpret_ansic) | |
+ { | |
+ if (c == 'U') | |
+ { | |
+ unsigned char buf[8]; | |
+ unsigned int n = 0; | |
+ int i; | |
+ | |
+ for (i = 0; i < 8; i++) | |
+ { | |
+ int c1 = phase2_getc (); | |
+ | |
+ if (c1 >= '0' && c1 <= '9') | |
+ n = (n << 4) + (c1 - '0'); | |
+ else if (c1 >= 'A' && c1 <= 'F') | |
+ n = (n << 4) + (c1 - 'A' + 10); | |
+ else if (c1 >= 'a' && c1 <= 'f') | |
+ n = (n << 4) + (c1 - 'a' + 10); | |
+ else | |
+ { | |
+ phase2_ungetc (c1); | |
+ while (--i >= 0) | |
+ phase2_ungetc (buf[i]); | |
+ phase2_ungetc (c); | |
+ ++*backslash_counter; | |
+ return UNICODE ('\\'); | |
+ } | |
+ | |
+ buf[i] = c1; | |
+ } | |
+ if (n < 0x110000) | |
+ { | |
+ *backslash_counter = 0; | |
+ return UNICODE (n); | |
+ } | |
+ | |
+ error_with_progname = false; | |
+ error (0, 0, _("%s:%d: warning: invalid Unicode character"), | |
+ logical_file_name, line_number); | |
+ error_with_progname = true; | |
+ | |
+ while (--i >= 0) | |
+ phase2_ungetc (buf[i]); | |
+ phase2_ungetc (c); | |
+ ++*backslash_counter; | |
+ return UNICODE ('\\'); | |
+ } | |
+ | |
+ if (c == 'N') | |
+ { | |
+ int c1 = phase2_getc (); | |
+ if (c1 == '{') | |
+ { | |
+ unsigned char buf[UNINAME_MAX + 1]; | |
+ int i; | |
+ unsigned int n; | |
+ | |
+ for (i = 0; i < UNINAME_MAX; i++) | |
+ { | |
+ int c2 = phase2_getc (); | |
+ if (!(c2 >= ' ' && c2 <= '~')) | |
+ { | |
+ phase2_ungetc (c2); | |
+ while (--i >= 0) | |
+ phase2_ungetc (buf[i]); | |
+ phase2_ungetc (c1); | |
+ phase2_ungetc (c); | |
+ ++*backslash_counter; | |
+ return UNICODE ('\\'); | |
+ } | |
+ if (c2 == '}') | |
+ break; | |
+ buf[i] = c2; | |
+ } | |
+ buf[i] = '\0'; | |
+ | |
+ n = unicode_name_character ((char *) buf); | |
+ if (n != UNINAME_INVALID) | |
+ { | |
+ *backslash_counter = 0; | |
+ return UNICODE (n); | |
+ } | |
+ | |
+ phase2_ungetc ('}'); | |
+ while (--i >= 0) | |
+ phase2_ungetc (buf[i]); | |
+ } | |
+ phase2_ungetc (c1); | |
+ phase2_ungetc (c); | |
+ ++*backslash_counter; | |
+ return UNICODE ('\\'); | |
+ } | |
+ } | |
+ } | |
+ | |
+ phase2_ungetc (c); | |
+ ++*backslash_counter; | |
+ return UNICODE ('\\'); | |
+ } | |
+} | |
+ | |
+ | |
+/* Combine characters into tokens. Discard whitespace except newlines at | |
+ the end of logical lines. */ | |
+ | |
+/* Number of pending open parentheses/braces/brackets. */ | |
+static int open_pbb; | |
+ | |
+static token_ty phase5_pushback[2]; | |
+static int phase5_pushback_length; | |
+ | |
+static token_type_ty last_token_type = token_type_other; | |
+ | |
+static void | |
+phase5_scan_regexp () | |
+{ | |
+ int c; | |
+ /* scan for end of RegExp literal ('/') */ | |
+ for (;;) | |
+ { | |
+ c = phase2_getc (); /* must use phase2 as there can't be comments */ | |
+ if (c == '/') | |
+ break; | |
+ switch (c) | |
+ { | |
+ case UEOF: | |
+ error_with_progname = false; | |
+ error (0, 0, _("%s:%d: warning: RegExp literal terminated too early"), | |
+ logical_file_name, line_number); | |
+ error_with_progname = true; | |
+ return; | |
+ | |
+ case '\n': | |
+ error_with_progname = false; | |
+ error (0, 0, _("%s:%d: warning: RegExp literal contains newline"), | |
+ logical_file_name, line_number); | |
+ error_with_progname = true; | |
+ return; | |
+ | |
+ case '\\': | |
+ { | |
+ int c2 = phase2_getc (); | |
+ if (c2 == UEOF) | |
+ { | |
+ error_with_progname = false; | |
+ error (0, 0, _("%s:%d: warning: RegExp literal terminated too early"), | |
+ logical_file_name, line_number); | |
+ error_with_progname = true; | |
+ return; | |
+ } | |
+ } | |
+ break; | |
+ | |
+ default: | |
+ continue; | |
+ } | |
+ } | |
+ /* scan for modifier flags (ECMA-262 5th section 15.10.4.1) */ | |
+ c = phase2_getc (); | |
+ if (c == 'g' || c == 'i' || c == 'm') | |
+ { | |
+ return; | |
+ } | |
+ else | |
+ { | |
+ phase2_ungetc (c); | |
+ } | |
+} | |
+ | |
+static void | |
+phase5_get (token_ty *tp) | |
+{ | |
+ int c; | |
+ | |
+ if (phase5_pushback_length) | |
+ { | |
+ *tp = phase5_pushback[--phase5_pushback_length]; | |
+ last_token_type = tp->type; | |
+ return; | |
+ } | |
+ | |
+ for (;;) | |
+ { | |
+ tp->line_number = line_number; | |
+ c = phase3_getc (); | |
+ | |
+ switch (c) | |
+ { | |
+ case UEOF: | |
+ tp->type = last_token_type = token_type_eof; | |
+ return; | |
+ | |
+ case ' ': | |
+ case '\t': | |
+ case '\f': | |
+ /* Ignore whitespace and comments. */ | |
+ continue; | |
+ | |
+ case '\n': | |
+ if (last_non_comment_line > last_comment_line) | |
+ savable_comment_reset (); | |
+ /* Ignore newline if and only if it is used for implicit line | |
+ joining. */ | |
+ if (open_pbb > 0) | |
+ continue; | |
+ tp->type = last_token_type = token_type_other; | |
+ return; | |
+ } | |
+ | |
+ last_non_comment_line = tp->line_number; | |
+ | |
+ switch (c) | |
+ { | |
+ case '.': | |
+ { | |
+ int c1 = phase3_getc (); | |
+ phase3_ungetc (c1); | |
+ if (!(c1 >= '0' && c1 <= '9')) | |
+ { | |
+ | |
+ tp->type = last_token_type = token_type_other; | |
+ return; | |
+ } | |
+ } | |
+ /* FALLTHROUGH */ | |
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': | |
+ case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': | |
+ case 'M': case 'N': case 'O': case 'P': case 'Q': | |
+ case 'S': case 'T': case 'V': case 'W': case 'X': | |
+ case 'Y': case 'Z': | |
+ case '_': | |
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': | |
+ case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': | |
+ case 'm': case 'n': case 'o': case 'p': case 'q': | |
+ case 's': case 't': case 'v': case 'w': case 'x': | |
+ case 'y': case 'z': | |
+ case '0': case '1': case '2': case '3': case '4': | |
+ case '5': case '6': case '7': case '8': case '9': | |
+ symbol: | |
+ /* Symbol, or part of a number. */ | |
+ { | |
+ static char *buffer; | |
+ static int bufmax; | |
+ int bufpos; | |
+ | |
+ bufpos = 0; | |
+ for (;;) | |
+ { | |
+ if (bufpos >= bufmax) | |
+ { | |
+ bufmax = 2 * bufmax + 10; | |
+ buffer = xrealloc (buffer, bufmax); | |
+ } | |
+ buffer[bufpos++] = c; | |
+ c = phase3_getc (); | |
+ switch (c) | |
+ { | |
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': | |
+ case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': | |
+ case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': | |
+ case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': | |
+ case 'Y': case 'Z': | |
+ case '_': | |
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': | |
+ case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': | |
+ case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': | |
+ case 's': case 't': case 'u': case 'v': case 'w': case 'x': | |
+ case 'y': case 'z': | |
+ case '0': case '1': case '2': case '3': case '4': | |
+ case '5': case '6': case '7': case '8': case '9': | |
+ continue; | |
+ default: | |
+ phase3_ungetc (c); | |
+ break; | |
+ } | |
+ break; | |
+ } | |
+ if (bufpos >= bufmax) | |
+ { | |
+ bufmax = 2 * bufmax + 10; | |
+ buffer = xrealloc (buffer, bufmax); | |
+ } | |
+ buffer[bufpos] = '\0'; | |
+ tp->string = xstrdup (buffer); | |
+ tp->type = last_token_type = token_type_symbol; | |
+ return; | |
+ } | |
+ | |
+ /* Strings. */ | |
+ { | |
+ struct mixed_string_buffer literal; | |
+ int quote_char; | |
+ bool interpret_ansic; | |
+ bool interpret_unicode; | |
+ unsigned int backslash_counter; | |
+ | |
+ case '"': case '\'': | |
+ quote_char = c; | |
+ interpret_ansic = true; | |
+ interpret_unicode = false; | |
+ string: | |
+ lexical_context = lc_string; | |
+ backslash_counter = 0; | |
+ /* Start accumulating the string. */ | |
+ init_mixed_string_buffer (&literal, lc_string); | |
+ for (;;) | |
+ { | |
+ int uc = phase7_getuc (quote_char, interpret_ansic, | |
+ interpret_unicode, &backslash_counter); | |
+ | |
+ if (uc == P7_EOF || uc == P7_STRING_END) | |
+ break; | |
+ | |
+ if (IS_UNICODE (uc)) | |
+ assert (UNICODE_VALUE (uc) >= 0 | |
+ && UNICODE_VALUE (uc) < 0x110000); | |
+ | |
+ mixed_string_buffer_append (&literal, uc); | |
+ } | |
+ tp->string = xstrdup (mixed_string_buffer_result (&literal)); | |
+ free_mixed_string_buffer (&literal); | |
+ tp->comment = add_reference (savable_comment); | |
+ lexical_context = lc_outside; | |
+ tp->type = last_token_type = token_type_string; | |
+ return; | |
+ } | |
+ | |
+ /* Identify operators. The multiple character ones are simply ignored | |
+ * as they are recognized here and are otherwise not relevant. */ | |
+ /* FALLTHROUGH */ | |
+ case '+': case '-': case '*': /* '/' is not listed here! */ | |
+ case '%': case '<': case '>': case '=': | |
+ case '~': case '!': case '|': case '&': case '^': | |
+ case '?': case ':': | |
+ tp->operator = c; | |
+ tp->type = last_token_type = token_type_operator; | |
+ return; | |
+ | |
+ case '/': | |
+ /* Either a division operator or the start of a RegExp literal. | |
+ * If the '/' token is spotted after a symbol it's a division, | |
+ * otherwise it's a regex */ | |
+ if (last_token_type == token_type_symbol || | |
+ last_token_type == token_type_rparen || | |
+ last_token_type == token_type_rbracket) | |
+ { | |
+ tp->operator = '/'; | |
+ tp->type = last_token_type = token_type_operator; | |
+ return; | |
+ } | |
+ else | |
+ { | |
+ phase5_scan_regexp (); | |
+ tp->type = last_token_type = token_type_other; | |
+ return; | |
+ } | |
+ | |
+ case '(': | |
+ open_pbb++; | |
+ tp->type = last_token_type = token_type_lparen; | |
+ return; | |
+ | |
+ case ')': | |
+ if (open_pbb > 0) | |
+ open_pbb--; | |
+ tp->type = last_token_type = token_type_rparen; | |
+ return; | |
+ | |
+ case ',': | |
+ tp->type = last_token_type = token_type_comma; | |
+ return; | |
+ | |
+ case '[': case '{': | |
+ open_pbb++; | |
+ tp->type = last_token_type = (c == '[' ? token_type_lbracket : token_type_other); | |
+ return; | |
+ | |
+ case ']': case '}': | |
+ if (open_pbb > 0) | |
+ open_pbb--; | |
+ tp->type = last_token_type = (c == ']' ? token_type_rbracket : token_type_other); | |
+ return; | |
+ | |
+ default: | |
+ /* We could carefully recognize each of the 2 and 3 character | |
+ operators, but it is not necessary, as we only need to recognize | |
+ gettext invocations. Don't bother. */ | |
+ tp->type = last_token_type = token_type_other; | |
+ return; | |
+ } | |
+ } | |
+} | |
+ | |
+/* Supports only one pushback token. */ | |
+static void | |
+phase5_unget (token_ty *tp) | |
+{ | |
+ if (tp->type != token_type_eof) | |
+ { | |
+ if (phase5_pushback_length == SIZEOF (phase5_pushback)) | |
+ abort (); | |
+ phase5_pushback[phase5_pushback_length++] = *tp; | |
+ } | |
+} | |
+ | |
+ | |
+/* Combine adjacent strings to form a single string. Note that the end | |
+ of a logical line appears as a token of its own, therefore strings that | |
+ belong to different logical lines will not be concatenated. */ | |
+ | |
+static void | |
+x_javascript_lex (token_ty *tp) | |
+{ | |
+ phase5_get (tp); | |
+ if (tp->type != token_type_string) | |
+ return; | |
+ for (;;) | |
+ { | |
+ token_ty tmp; | |
+ size_t len; | |
+ | |
+ phase5_get (&tmp); | |
+ if (tmp.type == token_type_operator && tmp.operator == '+') | |
+ { | |
+ /* allow concatenation of strings using the plus '+' operator */ | |
+ token_ty tmp2; | |
+ phase5_get (&tmp2); | |
+ if (tmp2.type != token_type_string) | |
+ { | |
+ phase5_unget (&tmp2); | |
+ phase5_unget (&tmp); | |
+ return; | |
+ } | |
+ memcpy(&tmp, &tmp2, sizeof(tmp)); | |
+ } | |
+ else if (tmp.type != token_type_string) | |
+ { | |
+ phase5_unget (&tmp); | |
+ return; | |
+ } | |
+ len = strlen (tp->string); | |
+ tp->string = xrealloc (tp->string, len + strlen (tmp.string) + 1); | |
+ strcpy (tp->string + len, tmp.string); | |
+ free (tmp.string); | |
+ } | |
+} | |
+ | |
+ | |
+/* ========================= Extracting strings. ========================== */ | |
+ | |
+ | |
+/* Context lookup table. */ | |
+static flag_context_list_table_ty *flag_context_list_table; | |
+ | |
+ | |
+/* The file is broken into tokens. Scan the token stream, looking for | |
+ a keyword, followed by a left paren, followed by a string. When we | |
+ see this sequence, we have something to remember. We assume we are | |
+ looking at a valid Javascript program, and leave the complaints about | |
+ the grammar to the compiler. | |
+ | |
+ Normal handling: Look for | |
+ keyword ( ... msgid ... ) | |
+ Plural handling: Look for | |
+ keyword ( ... msgid ... msgid_plural ... ) | |
+ | |
+ We use recursion because the arguments before msgid or between msgid | |
+ and msgid_plural can contain subexpressions of the same form. */ | |
+ | |
+ | |
+/* Extract messages until the next balanced closing parenthesis or bracket. | |
+ Extracted messages are added to MLP. | |
+ DELIM can be either token_type_rparen or token_type_rbracket, or | |
+ token_type_eof to accept both. | |
+ Return true upon eof, false upon closing parenthesis or bracket. */ | |
+static bool | |
+extract_balanced (message_list_ty *mlp, | |
+ token_type_ty delim, | |
+ flag_context_ty outer_context, | |
+ flag_context_list_iterator_ty context_iter, | |
+ struct arglist_parser *argparser) | |
+{ | |
+ /* Current argument number. */ | |
+ int arg = 1; | |
+ /* 0 when no keyword has been seen. 1 right after a keyword is seen. */ | |
+ int state; | |
+ /* Parameters of the keyword just seen. Defined only in state 1. */ | |
+ const struct callshapes *next_shapes = NULL; | |
+ /* Context iterator that will be used if the next token is a '('. */ | |
+ flag_context_list_iterator_ty next_context_iter = | |
+ passthrough_context_list_iterator; | |
+ /* Current context. */ | |
+ flag_context_ty inner_context = | |
+ inherited_context (outer_context, | |
+ flag_context_list_iterator_advance (&context_iter)); | |
+ | |
+ /* Start state is 0. */ | |
+ state = 0; | |
+ | |
+ for (;;) | |
+ { | |
+ token_ty token; | |
+ | |
+ x_javascript_lex (&token); | |
+ switch (token.type) | |
+ { | |
+ case token_type_symbol: | |
+ { | |
+ void *keyword_value; | |
+ | |
+ if (hash_find_entry (&keywords, token.string, strlen (token.string), | |
+ &keyword_value) | |
+ == 0) | |
+ { | |
+ next_shapes = (const struct callshapes *) keyword_value; | |
+ state = 1; | |
+ } | |
+ else | |
+ state = 0; | |
+ } | |
+ next_context_iter = | |
+ flag_context_list_iterator ( | |
+ flag_context_list_table_lookup ( | |
+ flag_context_list_table, | |
+ token.string, strlen (token.string))); | |
+ free (token.string); | |
+ continue; | |
+ | |
+ case token_type_lparen: | |
+ if (extract_balanced (mlp, token_type_rparen, | |
+ inner_context, next_context_iter, | |
+ arglist_parser_alloc (mlp, | |
+ state ? next_shapes : NULL))) | |
+ { | |
+ xgettext_current_source_encoding = po_charset_utf8; | |
+ arglist_parser_done (argparser, arg); | |
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding; | |
+ return true; | |
+ } | |
+ next_context_iter = null_context_list_iterator; | |
+ state = 0; | |
+ continue; | |
+ | |
+ case token_type_rparen: | |
+ if (delim == token_type_rparen || delim == token_type_eof) | |
+ { | |
+ xgettext_current_source_encoding = po_charset_utf8; | |
+ arglist_parser_done (argparser, arg); | |
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding; | |
+ return false; | |
+ } | |
+ next_context_iter = null_context_list_iterator; | |
+ state = 0; | |
+ continue; | |
+ | |
+ case token_type_comma: | |
+ arg++; | |
+ inner_context = | |
+ inherited_context (outer_context, | |
+ flag_context_list_iterator_advance ( | |
+ &context_iter)); | |
+ next_context_iter = passthrough_context_list_iterator; | |
+ state = 0; | |
+ continue; | |
+ | |
+ case token_type_lbracket: | |
+ if (extract_balanced (mlp, token_type_rbracket, | |
+ null_context, null_context_list_iterator, | |
+ arglist_parser_alloc (mlp, NULL))) | |
+ { | |
+ xgettext_current_source_encoding = po_charset_utf8; | |
+ arglist_parser_done (argparser, arg); | |
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding; | |
+ return true; | |
+ } | |
+ next_context_iter = null_context_list_iterator; | |
+ state = 0; | |
+ continue; | |
+ | |
+ case token_type_rbracket: | |
+ if (delim == token_type_rbracket || delim == token_type_eof) | |
+ { | |
+ xgettext_current_source_encoding = po_charset_utf8; | |
+ arglist_parser_done (argparser, arg); | |
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding; | |
+ return false; | |
+ } | |
+ next_context_iter = null_context_list_iterator; | |
+ state = 0; | |
+ continue; | |
+ | |
+ case token_type_string: | |
+ { | |
+ lex_pos_ty pos; | |
+ pos.file_name = logical_file_name; | |
+ pos.line_number = token.line_number; | |
+ | |
+ xgettext_current_source_encoding = po_charset_utf8; | |
+ if (extract_all) | |
+ remember_a_message (mlp, NULL, token.string, inner_context, | |
+ &pos, NULL, token.comment); | |
+ else | |
+ arglist_parser_remember (argparser, arg, token.string, | |
+ inner_context, | |
+ pos.file_name, pos.line_number, | |
+ token.comment); | |
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding; | |
+ } | |
+ drop_reference (token.comment); | |
+ next_context_iter = null_context_list_iterator; | |
+ state = 0; | |
+ continue; | |
+ | |
+ case token_type_eof: | |
+ xgettext_current_source_encoding = po_charset_utf8; | |
+ arglist_parser_done (argparser, arg); | |
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding; | |
+ return true; | |
+ | |
+ /* FALLTHROUGH */ | |
+ case token_type_regexp: | |
+ case token_type_operator: | |
+ case token_type_other: | |
+ next_context_iter = null_context_list_iterator; | |
+ state = 0; | |
+ continue; | |
+ | |
+ default: | |
+ abort (); | |
+ } | |
+ } | |
+} | |
+ | |
+ | |
+void | |
+extract_javascript (FILE *f, | |
+ const char *real_filename, const char *logical_filename, | |
+ flag_context_list_table_ty *flag_table, | |
+ msgdomain_list_ty *mdlp) | |
+{ | |
+ message_list_ty *mlp = mdlp->item[0]->messages; | |
+ | |
+ fp = f; | |
+ real_file_name = real_filename; | |
+ logical_file_name = xstrdup (logical_filename); | |
+ line_number = 1; | |
+ | |
+ lexical_context = lc_outside; | |
+ | |
+ last_comment_line = -1; | |
+ last_non_comment_line = -1; | |
+ | |
+ xgettext_current_file_source_encoding = xgettext_global_source_encoding; | |
+#if HAVE_ICONV | |
+ xgettext_current_file_source_iconv = xgettext_global_source_iconv; | |
+#endif | |
+ | |
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding; | |
+#if HAVE_ICONV | |
+ xgettext_current_source_iconv = xgettext_current_file_source_iconv; | |
+#endif | |
+ | |
+ continuation_or_nonblank_line = false; | |
+ | |
+ open_pbb = 0; | |
+ | |
+ flag_context_list_table = flag_table; | |
+ | |
+ init_keywords (); | |
+ | |
+ /* Eat tokens until eof is seen. When extract_balanced returns | |
+ due to an unbalanced closing parenthesis, just restart it. */ | |
+ while (!extract_balanced (mlp, token_type_eof, | |
+ null_context, null_context_list_iterator, | |
+ arglist_parser_alloc (mlp, NULL))) | |
+ ; | |
+ | |
+ fp = NULL; | |
+ real_file_name = NULL; | |
+ logical_file_name = NULL; | |
+ line_number = 0; | |
+} | |
diff --git c/gettext-tools/src/x-javascript.h w/gettext-tools/src/x-javascript.h | |
new file mode 100644 | |
index 0000000..d668136 | |
--- /dev/null | |
+++ w/gettext-tools/src/x-javascript.h | |
@@ -0,0 +1,52 @@ | |
+/* xgettext Python backend. | |
+ Copyright (C) 2002-2003, 2006 Free Software Foundation, Inc. | |
+ This file was written by Andreas Stricker <[email protected]>, 2010. | |
+ It's based on x-python from Bruno Haible. | |
+ | |
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>. */ | |
+ | |
+ | |
+#include <stdio.h> | |
+ | |
+#include "message.h" | |
+#include "xgettext.h" | |
+ | |
+ | |
+#ifdef __cplusplus | |
+extern "C" { | |
+#endif | |
+ | |
+ | |
+#define EXTENSIONS_JAVASCRIPT \ | |
+ { "js", "JavaScript" }, \ | |
+ | |
+#define SCANNERS_JAVASCRIPT \ | |
+ { "JavaScript", extract_javascript, \ | |
+ &flag_table_javascript, &formatstring_javascript, NULL }, \ | |
+ | |
+/* Scan a Python file and add its translatable strings to mdlp. */ | |
+extern void extract_javascript (FILE *fp, const char *real_filename, | |
+ const char *logical_filename, | |
+ flag_context_list_table_ty *flag_table, | |
+ msgdomain_list_ty *mdlp); | |
+ | |
+extern void x_javascript_keyword (const char *keyword); | |
+extern void x_javascript_extract_all (void); | |
+ | |
+extern void init_flag_table_javascript (void); | |
+ | |
+ | |
+#ifdef __cplusplus | |
+} | |
+#endif | |
diff --git c/gettext-tools/src/xgettext.c w/gettext-tools/src/xgettext.c | |
index 919f30b..fc2d127 100644 | |
--- c/gettext-tools/src/xgettext.c | |
+++ w/gettext-tools/src/xgettext.c | |
@@ -82,6 +82,7 @@ | |
#include "x-scheme.h" | |
#include "x-smalltalk.h" | |
#include "x-java.h" | |
+#include "x-javascript.h" | |
#include "x-properties.h" | |
#include "x-csharp.h" | |
#include "x-awk.h" | |
@@ -154,6 +155,7 @@ static flag_context_list_table_ty flag_table_elisp; | |
static flag_context_list_table_ty flag_table_librep; | |
static flag_context_list_table_ty flag_table_scheme; | |
static flag_context_list_table_ty flag_table_java; | |
+static flag_context_list_table_ty flag_table_javascript; | |
static flag_context_list_table_ty flag_table_csharp; | |
static flag_context_list_table_ty flag_table_awk; | |
static flag_context_list_table_ty flag_table_ycp; | |
@@ -325,6 +327,7 @@ main (int argc, char *argv[]) | |
init_flag_table_librep (); | |
init_flag_table_scheme (); | |
init_flag_table_java (); | |
+ init_flag_table_javascript (); | |
init_flag_table_csharp (); | |
init_flag_table_awk (); | |
init_flag_table_ycp (); | |
@@ -349,6 +352,7 @@ main (int argc, char *argv[]) | |
x_librep_extract_all (); | |
x_scheme_extract_all (); | |
x_java_extract_all (); | |
+ x_javascript_extract_all (); | |
x_csharp_extract_all (); | |
x_awk_extract_all (); | |
x_tcl_extract_all (); | |
@@ -426,6 +430,7 @@ main (int argc, char *argv[]) | |
x_librep_keyword (optarg); | |
x_scheme_keyword (optarg); | |
x_java_keyword (optarg); | |
+ x_javascript_keyword (optarg); | |
x_csharp_keyword (optarg); | |
x_awk_keyword (optarg); | |
x_tcl_keyword (optarg); | |
@@ -855,8 +860,9 @@ Choice of input file language:\n")); | |
-L, --language=NAME recognise the specified language\n\ | |
(C, C++, ObjectiveC, PO, Shell, Python, Lisp,\n\ | |
EmacsLisp, librep, Scheme, Smalltalk, Java,\n\ | |
- JavaProperties, C#, awk, YCP, Tcl, Perl, PHP,\n\ | |
- GCC-source, NXStringTable, RST, Glade)\n")); | |
+ JavaProperties, JavaScript, C#, awk, YCP, Tcl,\n\ | |
+ Perl, PHP, GCC-source, NXStringTable, RST,\n\ | |
+ Glade)\n")); | |
printf (_("\ | |
-C, --c++ shorthand for --language=C++\n")); | |
printf (_("\ | |
@@ -1700,6 +1706,11 @@ xgettext_record_flag (const char *optionstring) | |
name_start, name_end, | |
argnum, value, pass); | |
break; | |
+ case format_javascript: | |
+ flag_context_list_table_insert (&flag_table_javascript, 0, | |
+ name_start, name_end, | |
+ argnum, value, pass); | |
+ break; | |
case format_csharp: | |
flag_context_list_table_insert (&flag_table_csharp, 0, | |
name_start, name_end, | |
@@ -3171,6 +3182,7 @@ language_to_extractor (const char *name) | |
SCANNERS_SCHEME | |
SCANNERS_SMALLTALK | |
SCANNERS_JAVA | |
+ SCANNERS_JAVASCRIPT | |
SCANNERS_PROPERTIES | |
SCANNERS_CSHARP | |
SCANNERS_AWK | |
@@ -3254,6 +3266,7 @@ extension_to_language (const char *extension) | |
EXTENSIONS_SCHEME | |
EXTENSIONS_SMALLTALK | |
EXTENSIONS_JAVA | |
+ EXTENSIONS_JAVASCRIPT | |
EXTENSIONS_PROPERTIES | |
EXTENSIONS_CSHARP | |
EXTENSIONS_AWK | |
diff --git c/gettext-tools/tests/Makefile.am w/gettext-tools/tests/Makefile.am | |
index 0042a3b..2a78fc9 100644 | |
--- c/gettext-tools/tests/Makefile.am | |
+++ w/gettext-tools/tests/Makefile.am | |
@@ -82,6 +82,7 @@ TESTS = gettext-1 gettext-2 gettext-3 gettext-4 gettext-5 gettext-6 gettext-7 \ | |
xgettext-glade-1 xgettext-glade-2 xgettext-glade-3 xgettext-glade-4 \ | |
xgettext-java-1 xgettext-java-2 xgettext-java-3 xgettext-java-4 \ | |
xgettext-java-5 xgettext-java-6 xgettext-java-7 \ | |
+ xgettext-javascript-1 xgettext-javascript-2 xgettext-javascript-3 \ | |
xgettext-librep-1 xgettext-librep-2 \ | |
xgettext-lisp-1 xgettext-lisp-2 \ | |
xgettext-objc-1 xgettext-objc-2 \ | |
diff --git c/gettext-tools/tests/Makefile.in w/gettext-tools/tests/Makefile.in | |
index 017ca1d..b2d07c0 100644 | |
--- c/gettext-tools/tests/Makefile.in | |
+++ w/gettext-tools/tests/Makefile.in | |
@@ -1337,6 +1337,7 @@ TESTS = gettext-1 gettext-2 gettext-3 gettext-4 gettext-5 gettext-6 gettext-7 \ | |
xgettext-glade-1 xgettext-glade-2 xgettext-glade-3 xgettext-glade-4 \ | |
xgettext-java-1 xgettext-java-2 xgettext-java-3 xgettext-java-4 \ | |
xgettext-java-5 xgettext-java-6 xgettext-java-7 \ | |
+ xgettext-javascript-1 xgettext-javascript-2 xgettext-javascript-3 \ | |
xgettext-librep-1 xgettext-librep-2 \ | |
xgettext-lisp-1 xgettext-lisp-2 \ | |
xgettext-objc-1 xgettext-objc-2 \ | |
diff --git c/gettext-tools/tests/xgettext-javascript-1 w/gettext-tools/tests/xgettext-javascript-1 | |
new file mode 100644 | |
index 0000000..e99523d | |
--- /dev/null | |
+++ w/gettext-tools/tests/xgettext-javascript-1 | |
@@ -0,0 +1,64 @@ | |
+#!/bin/sh | |
+ | |
+# Test of Javascript support. | |
+ | |
+tmpfiles="" | |
+trap 'rm -fr $tmpfiles' 1 2 3 15 | |
+ | |
+tmpfiles="$tmpfiles xg-js-1.js" | |
+cat <<\EOF > xg-js-1.js | |
+var s1 = "Simple string, no gettext needed", | |
+ s2 = _("Extract this first string"); | |
+function foo(a) { | |
+ var s3 = "Prefix _(" + _("Extract this second string") + ") Postfix"; | |
+} | |
+if (document.getElementsById("foo")[0].innerHTML == _("Extract this thirth string")) { | |
+ /* _("This is a comment and must not be extracted!") */ | |
+} | |
+EOF | |
+ | |
+tmpfiles="$tmpfiles xg-js-1.err xg-js-1.tmp xg-js-1.pot" | |
+: ${XGETTEXT=xgettext} | |
+${XGETTEXT} --add-comments --no-location -o xg-js-1.tmp xg-js-1.js 2>xg-js-1.err | |
+test $? = 0 || { cat xg-js-1.err; rm -fr $tmpfiles; exit 1; } | |
+# Don't simplify this to "grep ... < xg-js-1.tmp", otherwise OpenBSD 4.0 grep | |
+# only outputs "Binary file (standard input) matches". | |
+cat xg-js-1.tmp | grep -v 'POT-Creation-Date' | LC_ALL=C tr -d '\r' > xg-js-1.pot | |
+ | |
+tmpfiles="$tmpfiles xg-js-1.ok" | |
+cat <<\EOF > xg-js-1.ok | |
+# SOME DESCRIPTIVE TITLE. | |
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER | |
+# This file is distributed under the same license as the PACKAGE package. | |
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. | |
+# | |
+#, fuzzy | |
+msgid "" | |
+msgstr "" | |
+"Project-Id-Version: PACKAGE VERSION\n" | |
+"Report-Msgid-Bugs-To: \n" | |
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | |
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | |
+"Language-Team: LANGUAGE <[email protected]>\n" | |
+"Language: \n" | |
+"MIME-Version: 1.0\n" | |
+"Content-Type: text/plain; charset=CHARSET\n" | |
+"Content-Transfer-Encoding: 8bit\n" | |
+ | |
+msgid "Extract this first string" | |
+msgstr "" | |
+ | |
+msgid "Extract this second string" | |
+msgstr "" | |
+ | |
+msgid "Extract this thirth string" | |
+msgstr "" | |
+EOF | |
+ | |
+: ${DIFF=diff} | |
+${DIFF} xg-js-1.ok xg-js-1.pot | |
+result=$? | |
+ | |
+rm -fr $tmpfiles | |
+ | |
+exit $result | |
diff --git c/gettext-tools/tests/xgettext-javascript-2 w/gettext-tools/tests/xgettext-javascript-2 | |
new file mode 100644 | |
index 0000000..9766646 | |
--- /dev/null | |
+++ w/gettext-tools/tests/xgettext-javascript-2 | |
@@ -0,0 +1,91 @@ | |
+#!/bin/sh | |
+ | |
+# Test of Javascript support. | |
+# Playing with regex and division operator | |
+ | |
+tmpfiles="" | |
+trap 'rm -fr $tmpfiles' 1 2 3 15 | |
+ | |
+tmpfiles="$tmpfiles xg-js-2.js" | |
+cat <<\EOF > xg-js-2.js | |
+// RegExp literals containing string quotes must not desync the parser | |
+var d = 1 / 2 / 4; | |
+var s = " x " + /^\d/.match("0815").replace(/[a-z]/g, '@'); | |
+var s1 = /"/.match(_("RegExp test string #1")); | |
+var s2 = /'/.match(_("RegExp test string #2")); | |
+var s3 = /['a-b]/.match(_('RegExp test string #3')); | |
+var s4 = /["a-b]/.match(_('RegExp test string #4')); | |
+var s5 = /[a-b']/.match(_('RegExp test string #5')); | |
+var s6 = /[a-b"]/.match(_('RegExp test string #6')); | |
+var c = 35 / 2 / 8 + _("RegExp test string #7").length / 32.0; | |
+var sizestr = Math.round(size/1024*factor)/factor+_("RegExp test string #8"); | |
+var cssClassType = attr.type.replace(/^.*\//, _('RegExp test string #9')).replace(/\./g, '-'); | |
+var lookup = lookuptable[idx]/factor+_("RegExp test string #10"); | |
+EOF | |
+ | |
+tmpfiles="$tmpfiles xg-js-2.err xg-js-2.tmp xg-js-2.pot" | |
+: ${XGETTEXT=xgettext} | |
+${XGETTEXT} --add-comments --no-location -o xg-js-2.tmp xg-js-2.js 2>xg-js-2.err | |
+test $? = 0 || { cat xg-js-2.err; rm -fr $tmpfiles; exit 1; } | |
+# Don't simplify this to "grep ... < xg-js-2.tmp", otherwise OpenBSD 4.0 grep | |
+# only outputs "Binary file (standard input) matches". | |
+cat xg-js-2.tmp | grep -v 'POT-Creation-Date' | LC_ALL=C tr -d '\r' > xg-js-2.pot | |
+ | |
+tmpfiles="$tmpfiles xg-js-2.ok" | |
+cat <<\EOF > xg-js-2.ok | |
+# SOME DESCRIPTIVE TITLE. | |
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER | |
+# This file is distributed under the same license as the PACKAGE package. | |
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. | |
+# | |
+#, fuzzy | |
+msgid "" | |
+msgstr "" | |
+"Project-Id-Version: PACKAGE VERSION\n" | |
+"Report-Msgid-Bugs-To: \n" | |
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | |
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | |
+"Language-Team: LANGUAGE <[email protected]>\n" | |
+"Language: \n" | |
+"MIME-Version: 1.0\n" | |
+"Content-Type: text/plain; charset=CHARSET\n" | |
+"Content-Transfer-Encoding: 8bit\n" | |
+ | |
+msgid "RegExp test string #1" | |
+msgstr "" | |
+ | |
+msgid "RegExp test string #2" | |
+msgstr "" | |
+ | |
+msgid "RegExp test string #3" | |
+msgstr "" | |
+ | |
+msgid "RegExp test string #4" | |
+msgstr "" | |
+ | |
+msgid "RegExp test string #5" | |
+msgstr "" | |
+ | |
+msgid "RegExp test string #6" | |
+msgstr "" | |
+ | |
+msgid "RegExp test string #7" | |
+msgstr "" | |
+ | |
+msgid "RegExp test string #8" | |
+msgstr "" | |
+ | |
+msgid "RegExp test string #9" | |
+msgstr "" | |
+ | |
+msgid "RegExp test string #10" | |
+msgstr "" | |
+EOF | |
+ | |
+: ${DIFF=diff} | |
+${DIFF} xg-js-2.ok xg-js-2.pot | |
+result=$? | |
+ | |
+rm -fr $tmpfiles | |
+ | |
+exit $result | |
diff --git c/gettext-tools/tests/xgettext-javascript-3 w/gettext-tools/tests/xgettext-javascript-3 | |
new file mode 100644 | |
index 0000000..44e5366 | |
--- /dev/null | |
+++ w/gettext-tools/tests/xgettext-javascript-3 | |
@@ -0,0 +1,73 @@ | |
+#!/bin/sh | |
+ | |
+# Test of Javascript support. | |
+# Playing with concatenation of string literals withing the gettext function | |
+ | |
+tmpfiles="" | |
+trap 'rm -fr $tmpfiles' 1 2 3 15 | |
+ | |
+tmpfiles="$tmpfiles xg-js-2.js" | |
+cat <<\EOF > xg-js-2.js | |
+// The usual way to concatenate strings is the plus '+' sign | |
+var s1 = _("Concatenation #1 " "- String part added"); | |
+var s2 = _('Concatenation #2 ' + '- String part added'); | |
+var s3 = _('Concatenation #3 ' | |
+ '- String part added'); | |
+var s4 = _('Concatenation #4 ' + | |
+ '- String part added'); | |
+var s5 = _("This" + " whole " | |
+ + "string" + | |
+ ' should' + " be " + 'extracted'); | |
+EOF | |
+ | |
+tmpfiles="$tmpfiles xg-js-2.err xg-js-2.tmp xg-js-2.pot" | |
+: ${XGETTEXT=xgettext} | |
+${XGETTEXT} --add-comments --no-location -o xg-js-2.tmp xg-js-2.js 2>xg-js-2.err | |
+test $? = 0 || { cat xg-js-2.err; rm -fr $tmpfiles; exit 1; } | |
+# Don't simplify this to "grep ... < xg-js-2.tmp", otherwise OpenBSD 4.0 grep | |
+# only outputs "Binary file (standard input) matches". | |
+cat xg-js-2.tmp | grep -v 'POT-Creation-Date' | LC_ALL=C tr -d '\r' > xg-js-2.pot | |
+ | |
+tmpfiles="$tmpfiles xg-js-2.ok" | |
+cat <<\EOF > xg-js-2.ok | |
+# SOME DESCRIPTIVE TITLE. | |
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER | |
+# This file is distributed under the same license as the PACKAGE package. | |
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. | |
+# | |
+#, fuzzy | |
+msgid "" | |
+msgstr "" | |
+"Project-Id-Version: PACKAGE VERSION\n" | |
+"Report-Msgid-Bugs-To: \n" | |
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | |
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | |
+"Language-Team: LANGUAGE <[email protected]>\n" | |
+"Language: \n" | |
+"MIME-Version: 1.0\n" | |
+"Content-Type: text/plain; charset=CHARSET\n" | |
+"Content-Transfer-Encoding: 8bit\n" | |
+ | |
+msgid "Concatenation #1 - String part added" | |
+msgstr "" | |
+ | |
+msgid "Concatenation #2 - String part added" | |
+msgstr "" | |
+ | |
+msgid "Concatenation #3 - String part added" | |
+msgstr "" | |
+ | |
+msgid "Concatenation #4 - String part added" | |
+msgstr "" | |
+ | |
+msgid "This whole string should be extracted" | |
+msgstr "" | |
+EOF | |
+ | |
+: ${DIFF=diff} | |
+${DIFF} xg-js-2.ok xg-js-2.pot | |
+result=$? | |
+ | |
+rm -fr $tmpfiles | |
+ | |
+exit $result | |
diff --git c/gettext-tools/woe32dll/gettextsrc-exports.c w/gettext-tools/woe32dll/gettextsrc-exports.c | |
index b1c4a53..f150e36 100644 | |
--- c/gettext-tools/woe32dll/gettextsrc-exports.c | |
+++ w/gettext-tools/woe32dll/gettextsrc-exports.c | |
@@ -29,6 +29,7 @@ VARIABLE(formatstring_elisp) | |
VARIABLE(formatstring_gcc_internal) | |
VARIABLE(formatstring_gfc_internal) | |
VARIABLE(formatstring_java) | |
+VARIABLE(formatstring_javascript) | |
VARIABLE(formatstring_kde) | |
VARIABLE(formatstring_librep) | |
VARIABLE(formatstring_lisp) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git c/gettext-tools/libgettextpo/Makefile.am i/gettext-tools/libgettextpo/Makefile.am | |
index cf4a928..cee9418 100644 | |
--- c/gettext-tools/libgettextpo/Makefile.am | |
+++ i/gettext-tools/libgettextpo/Makefile.am | |
@@ -70,6 +70,7 @@ libgettextpo_la_AUXSOURCES = \ | |
../src/format-librep.c \ | |
../src/format-scheme.c \ | |
../src/format-java.c \ | |
+ ../src/format-javascript.c \ | |
../src/format-csharp.c \ | |
../src/format-awk.c \ | |
../src/format-pascal.c \ | |
diff --git c/gettext-tools/libgettextpo/Makefile.in i/gettext-tools/libgettextpo/Makefile.in | |
index 20a60b8..589a373 100644 | |
--- c/gettext-tools/libgettextpo/Makefile.in | |
+++ i/gettext-tools/libgettextpo/Makefile.in | |
@@ -387,7 +387,7 @@ am__libgettextpo_la_SOURCES_DIST = gettext-po.c ../src/str-list.c \ | |
../src/plural-table.c ../src/format-c.c ../src/format-sh.c \ | |
../src/format-python.c ../src/format-lisp.c \ | |
../src/format-elisp.c ../src/format-librep.c \ | |
- ../src/format-scheme.c ../src/format-java.c \ | |
+ ../src/format-scheme.c ../src/format-java.c ../src/format-javascript.c \ | |
../src/format-csharp.c ../src/format-awk.c \ | |
../src/format-pascal.c ../src/format-ycp.c ../src/format-tcl.c \ | |
../src/format-perl.c ../src/format-perl-brace.c \ | |
@@ -403,7 +403,7 @@ am__objects_1 = str-list.lo dir-list.lo message.lo msgl-ascii.lo \ | |
read-po.lo read-catalog-abstract.lo read-catalog.lo \ | |
plural-table.lo format-c.lo format-sh.lo format-python.lo \ | |
format-lisp.lo format-elisp.lo format-librep.lo \ | |
- format-scheme.lo format-java.lo format-csharp.lo format-awk.lo \ | |
+ format-scheme.lo format-java.lo format-javascript.lo format-csharp.lo format-awk.lo \ | |
format-pascal.lo format-ycp.lo format-tcl.lo format-perl.lo \ | |
format-perl-brace.lo format-php.lo format-gcc-internal.lo \ | |
format-gfc-internal.lo format-qt.lo format-qt-plural.lo \ | |
@@ -1729,6 +1729,7 @@ libgettextpo_la_AUXSOURCES = \ | |
../src/format-librep.c \ | |
../src/format-scheme.c \ | |
../src/format-java.c \ | |
+ ../src/format-javascript.c \ | |
../src/format-csharp.c \ | |
../src/format-awk.c \ | |
../src/format-pascal.c \ | |
@@ -1983,6 +1984,10 @@ format-scheme.lo: ../src/format-scheme.c | |
format-java.lo: ../src/format-java.c | |
$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o format-java.lo `test -f '../src/format-java.c' || echo '$(srcdir)/'`../src/format-java.c | |
+format-javascript.lo: ../src/format-javascript.c | |
+ $(AM_V_CC) @AM_BACKSLASH@ | |
+ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o format-javascript.lo `test -f '../src/format-javascript.c' || echo '$(srcdir)/'`../src/format-javascript.c | |
+ | |
format-csharp.lo: ../src/format-csharp.c | |
$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o format-csharp.lo `test -f '../src/format-csharp.c' || echo '$(srcdir)/'`../src/format-csharp.c | |
diff --git c/gettext-tools/src/Makefile.am i/gettext-tools/src/Makefile.am | |
index 87cc358..829bea3 100644 | |
--- c/gettext-tools/src/Makefile.am | |
+++ i/gettext-tools/src/Makefile.am | |
@@ -51,7 +51,7 @@ write-qt.h \ | |
po-time.h plural-table.h lang-table.h format.h filters.h \ | |
xgettext.h x-c.h x-po.h x-sh.h x-python.h x-lisp.h x-elisp.h x-librep.h \ | |
x-scheme.h x-smalltalk.h x-java.h x-properties.h x-csharp.h x-awk.h x-ycp.h \ | |
-x-tcl.h x-perl.h x-php.h x-stringtable.h x-rst.h x-glade.h | |
+x-tcl.h x-perl.h x-php.h x-stringtable.h x-rst.h x-glade.h x-javascript.h | |
EXTRA_DIST += FILES project-id ChangeLog.0 | |
@@ -135,7 +135,8 @@ FORMAT_SOURCE += \ | |
format-qt.c \ | |
format-qt-plural.c \ | |
format-kde.c \ | |
- format-boost.c | |
+ format-boost.c \ | |
+ format-javascript.c | |
# libgettextsrc contains all code that is needed by at least two programs. | |
libgettextsrc_la_SOURCES = \ | |
@@ -173,7 +174,7 @@ endif | |
xgettext_SOURCES += \ | |
x-c.c x-po.c x-sh.c x-python.c x-lisp.c x-elisp.c x-librep.c x-scheme.c \ | |
x-smalltalk.c x-java.c x-csharp.c x-awk.c x-ycp.c x-tcl.c x-perl.c x-php.c \ | |
- x-rst.c x-glade.c | |
+ x-rst.c x-glade.c x-javascript.c | |
if !WOE32DLL | |
msgattrib_SOURCES = msgattrib.c | |
else | |
diff --git c/gettext-tools/src/Makefile.in i/gettext-tools/src/Makefile.in | |
index ceeeb72..a37e6ba 100644 | |
--- c/gettext-tools/src/Makefile.in | |
+++ i/gettext-tools/src/Makefile.in | |
@@ -375,7 +375,7 @@ am__objects_1 = message.lo po-error.lo po-xerror.lo \ | |
@WOE32DLL_FALSE@ format-php.lo format-gcc-internal.lo \ | |
@WOE32DLL_FALSE@ format-gfc-internal.lo format-qt.lo \ | |
@WOE32DLL_FALSE@ format-qt-plural.lo format-kde.lo \ | |
-@WOE32DLL_FALSE@ format-boost.lo | |
+@WOE32DLL_FALSE@ format-boost.lo format-javascript.lo | |
@WOE32DLL_TRUE@am__objects_2 = c++format.lo format-c.lo format-sh.lo \ | |
@WOE32DLL_TRUE@ format-python.lo format-lisp.lo format-elisp.lo \ | |
@WOE32DLL_TRUE@ format-librep.lo format-scheme.lo \ | |
@@ -385,7 +385,7 @@ am__objects_1 = message.lo po-error.lo po-xerror.lo \ | |
@WOE32DLL_TRUE@ format-php.lo format-gcc-internal.lo \ | |
@WOE32DLL_TRUE@ format-gfc-internal.lo format-qt.lo \ | |
@WOE32DLL_TRUE@ format-qt-plural.lo format-kde.lo \ | |
-@WOE32DLL_TRUE@ format-boost.lo | |
+@WOE32DLL_TRUE@ format-boost.lo format-javascript.lo | |
@WOE32DLL_TRUE@am__objects_3 = gettextsrc-exports.lo | |
am_libgettextsrc_la_OBJECTS = $(am__objects_1) read-catalog.lo \ | |
color.lo write-catalog.lo write-properties.lo \ | |
@@ -508,7 +508,7 @@ urlget_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ | |
am__xgettext_SOURCES_DIST = xgettext.c x-c.c x-po.c x-sh.c x-python.c \ | |
x-lisp.c x-elisp.c x-librep.c x-scheme.c x-smalltalk.c \ | |
x-java.c x-csharp.c x-awk.c x-ycp.c x-tcl.c x-perl.c x-php.c \ | |
- x-rst.c x-glade.c ../woe32dll/c++xgettext.cc | |
+ x-rst.c x-glade.c x-javascript.c ../woe32dll/c++xgettext.cc | |
@WOE32DLL_FALSE@am_xgettext_OBJECTS = xgettext-xgettext.$(OBJEXT) \ | |
@WOE32DLL_FALSE@ xgettext-x-c.$(OBJEXT) xgettext-x-po.$(OBJEXT) \ | |
@WOE32DLL_FALSE@ xgettext-x-sh.$(OBJEXT) \ | |
@@ -526,7 +526,8 @@ am__xgettext_SOURCES_DIST = xgettext.c x-c.c x-po.c x-sh.c x-python.c \ | |
@WOE32DLL_FALSE@ xgettext-x-perl.$(OBJEXT) \ | |
@WOE32DLL_FALSE@ xgettext-x-php.$(OBJEXT) \ | |
@WOE32DLL_FALSE@ xgettext-x-rst.$(OBJEXT) \ | |
-@WOE32DLL_FALSE@ xgettext-x-glade.$(OBJEXT) | |
+@WOE32DLL_FALSE@ xgettext-x-glade.$(OBJEXT) \ | |
+@WOE32DLL_FALSE@ xgettext-x-javascript.$(OBJEXT) | |
@WOE32DLL_TRUE@am_xgettext_OBJECTS = xgettext-c++xgettext.$(OBJEXT) \ | |
@WOE32DLL_TRUE@ xgettext-x-c.$(OBJEXT) xgettext-x-po.$(OBJEXT) \ | |
@WOE32DLL_TRUE@ xgettext-x-sh.$(OBJEXT) \ | |
@@ -544,7 +545,8 @@ am__xgettext_SOURCES_DIST = xgettext.c x-c.c x-po.c x-sh.c x-python.c \ | |
@WOE32DLL_TRUE@ xgettext-x-perl.$(OBJEXT) \ | |
@WOE32DLL_TRUE@ xgettext-x-php.$(OBJEXT) \ | |
@WOE32DLL_TRUE@ xgettext-x-rst.$(OBJEXT) \ | |
-@WOE32DLL_TRUE@ xgettext-x-glade.$(OBJEXT) | |
+@WOE32DLL_TRUE@ xgettext-x-glade.$(OBJEXT) \ | |
+@WOE32DLL_TRUE@ xgettext-x-javascript.$(OBJEXT) | |
xgettext_OBJECTS = $(am_xgettext_OBJECTS) | |
AM_V_P = $(am__v_P_@AM_V@) | |
am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) | |
@@ -1746,7 +1748,7 @@ write-qt.h \ | |
po-time.h plural-table.h lang-table.h format.h filters.h \ | |
xgettext.h x-c.h x-po.h x-sh.h x-python.h x-lisp.h x-elisp.h x-librep.h \ | |
x-scheme.h x-smalltalk.h x-java.h x-properties.h x-csharp.h x-awk.h x-ycp.h \ | |
-x-tcl.h x-perl.h x-php.h x-stringtable.h x-rst.h x-glade.h | |
+x-tcl.h x-perl.h x-php.h x-stringtable.h x-rst.h x-glade.h x-javascript.h | |
aliaspath = $(localedir) | |
jardir = $(datadir)/gettext | |
@@ -1788,7 +1790,8 @@ dir-list.c str-list.c | |
@WOE32DLL_FALSE@ format-tcl.c format-perl.c format-perl-brace.c \ | |
@WOE32DLL_FALSE@ format-php.c format-gcc-internal.c \ | |
@WOE32DLL_FALSE@ format-gfc-internal.c format-qt.c \ | |
-@WOE32DLL_FALSE@ format-qt-plural.c format-kde.c format-boost.c | |
+@WOE32DLL_FALSE@ format-qt-plural.c format-kde.c format-boost.c \ | |
+@WOE32DLL_FALSE@ format-javascript.c | |
@WOE32DLL_TRUE@FORMAT_SOURCE = ../woe32dll/c++format.cc \ | |
@WOE32DLL_TRUE@ format-invalid.h format-c.c format-c-parse.h \ | |
@WOE32DLL_TRUE@ format-sh.c format-python.c format-lisp.c \ | |
@@ -1798,7 +1801,7 @@ dir-list.c str-list.c | |
@WOE32DLL_TRUE@ format-perl.c format-perl-brace.c format-php.c \ | |
@WOE32DLL_TRUE@ format-gcc-internal.c format-gfc-internal.c \ | |
@WOE32DLL_TRUE@ format-qt.c format-qt-plural.c format-kde.c \ | |
-@WOE32DLL_TRUE@ format-boost.c | |
+@WOE32DLL_TRUE@ format-boost.c format-javascript.c | |
# libgettextsrc contains all code that is needed by at least two programs. | |
libgettextsrc_la_SOURCES = $(COMMON_SOURCE) read-catalog.c color.c \ | |
@@ -1826,12 +1829,12 @@ msgunfmt_SOURCES = msgunfmt.c read-mo.c read-java.c read-csharp.c \ | |
@WOE32DLL_FALSE@ x-python.c x-lisp.c x-elisp.c x-librep.c \ | |
@WOE32DLL_FALSE@ x-scheme.c x-smalltalk.c x-java.c x-csharp.c \ | |
@WOE32DLL_FALSE@ x-awk.c x-ycp.c x-tcl.c x-perl.c x-php.c \ | |
-@WOE32DLL_FALSE@ x-rst.c x-glade.c | |
+@WOE32DLL_FALSE@ x-rst.c x-glade.c x-javascript.c | |
@WOE32DLL_TRUE@xgettext_SOURCES = ../woe32dll/c++xgettext.cc x-c.c \ | |
@WOE32DLL_TRUE@ x-po.c x-sh.c x-python.c x-lisp.c x-elisp.c \ | |
@WOE32DLL_TRUE@ x-librep.c x-scheme.c x-smalltalk.c x-java.c \ | |
@WOE32DLL_TRUE@ x-csharp.c x-awk.c x-ycp.c x-tcl.c x-perl.c \ | |
-@WOE32DLL_TRUE@ x-php.c x-rst.c x-glade.c | |
+@WOE32DLL_TRUE@ x-php.c x-rst.c x-glade.c x-javascript.c | |
@WOE32DLL_FALSE@msgattrib_SOURCES = msgattrib.c | |
@WOE32DLL_TRUE@msgattrib_SOURCES = ../woe32dll/c++msgattrib.cc | |
@WOE32DLL_FALSE@msgcat_SOURCES = msgcat.c | |
@@ -2650,6 +2653,14 @@ xgettext-x-glade.o: x-glade.c | |
xgettext-x-glade.obj: x-glade.c | |
$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-glade.obj `if test -f 'x-glade.c'; then $(CYGPATH_W) 'x-glade.c'; else $(CYGPATH_W) '$(srcdir)/x-glade.c'; fi` | |
+xgettext-x-javascript.o: x-javascript.c | |
+ $(AM_V_CC) @AM_BACKSLASH@ | |
+ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-javascript.o `test -f 'x-javascript.c' || echo '$(srcdir)/'`x-javascript.c | |
+ | |
+xgettext-x-javascript.obj: x-javascript.c | |
+ $(AM_V_CC) @AM_BACKSLASH@ | |
+ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-javascript.obj `if test -f 'x-javascript.c'; then $(CYGPATH_W) 'x-javascript.c'; else $(CYGPATH_W) '$(srcdir)/x-javascript.c'; fi` | |
+ | |
.cc.o: | |
$(AM_V_CXX)$(CXXCOMPILE) -c -o $@ $< | |
diff --git c/gettext-tools/src/format-javascript.c i/gettext-tools/src/format-javascript.c | |
new file mode 100644 | |
index 0000000..5705972 | |
--- /dev/null | |
+++ i/gettext-tools/src/format-javascript.c | |
@@ -0,0 +1,669 @@ | |
+/* JavaScript format strings. | |
+ Copyright (C) 2001-2004, 2006-2009 Free Software Foundation, Inc. | |
+ Written by Andreas Stricker <[email protected]>, 2010. | |
+ It's based on python format module from Bruno Haible. | |
+ | |
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>. */ | |
+ | |
+#ifdef HAVE_CONFIG_H | |
+# include <config.h> | |
+#endif | |
+ | |
+#include <stdbool.h> | |
+#include <stdlib.h> | |
+#include <string.h> | |
+ | |
+#include "format.h" | |
+#include "c-ctype.h" | |
+#include "xalloc.h" | |
+#include "xvasprintf.h" | |
+#include "format-invalid.h" | |
+#include "gettext.h" | |
+ | |
+#define _(str) gettext (str) | |
+ | |
+/* Gettext itself doesn't know any format string. But there are | |
+ plenty of libraries using any kind of format strings: | |
+ * JS-Gettext allows %1 %2 ... | |
+ * Prototype JS has templates #{name} | |
+ * ExtJS has String.format with {1} {2} ... and Templates with {name} | |
+ */ | |
+ | |
+enum format_arg_type | |
+{ | |
+ FAT_NONE, | |
+ FAT_ANY, | |
+ FAT_CHARACTER, | |
+ FAT_STRING, | |
+ FAT_INTEGER, | |
+ FAT_FLOAT | |
+}; | |
+ | |
+struct named_arg | |
+{ | |
+ char *name; | |
+ enum format_arg_type type; | |
+}; | |
+ | |
+struct unnamed_arg | |
+{ | |
+ enum format_arg_type type; | |
+}; | |
+ | |
+struct spec | |
+{ | |
+ unsigned int directives; | |
+ unsigned int named_arg_count; | |
+ unsigned int unnamed_arg_count; | |
+ unsigned int allocated; | |
+ struct named_arg *named; | |
+ struct unnamed_arg *unnamed; | |
+}; | |
+ | |
+/* 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) | |
+ | |
+ | |
+static int | |
+named_arg_compare (const void *p1, const void *p2) | |
+{ | |
+ return strcmp (((const struct named_arg *) p1)->name, | |
+ ((const struct named_arg *) p2)->name); | |
+} | |
+ | |
+#define INVALID_MIXES_NAMED_UNNAMED() \ | |
+ xstrdup (_("The string refers to arguments both through argument names and through unnamed argument specifications.")) | |
+ | |
+static void * | |
+format_parse (const char *format, bool translated, char *fdi, | |
+ char **invalid_reason) | |
+{ | |
+ const char *const format_start = format; | |
+ struct spec spec; | |
+ struct spec *result; | |
+ | |
+ spec.directives = 0; | |
+ spec.named_arg_count = 0; | |
+ spec.unnamed_arg_count = 0; | |
+ spec.allocated = 0; | |
+ spec.named = NULL; | |
+ spec.unnamed = NULL; | |
+ | |
+ for (; *format != '\0';) | |
+ if (*format++ == '%') | |
+ { | |
+ /* A directive. */ | |
+ char *name = NULL; | |
+ bool zero_precision = false; | |
+ enum format_arg_type type; | |
+ | |
+ FDI_SET (format - 1, FMTDIR_START); | |
+ spec.directives++; | |
+ | |
+ if (*format == '(') | |
+ { | |
+ unsigned int depth; | |
+ const char *name_start; | |
+ const char *name_end; | |
+ size_t n; | |
+ | |
+ name_start = ++format; | |
+ depth = 0; | |
+ for (; *format != '\0'; format++) | |
+ { | |
+ if (*format == '(') | |
+ depth++; | |
+ else if (*format == ')') | |
+ { | |
+ if (depth == 0) | |
+ break; | |
+ else | |
+ depth--; | |
+ } | |
+ } | |
+ if (*format == '\0') | |
+ { | |
+ *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); | |
+ FDI_SET (format - 1, FMTDIR_ERROR); | |
+ goto bad_format; | |
+ } | |
+ name_end = format++; | |
+ | |
+ n = name_end - name_start; | |
+ name = XNMALLOC (n + 1, char); | |
+ memcpy (name, name_start, n); | |
+ name[n] = '\0'; | |
+ } | |
+ | |
+ while (*format == '-' || *format == '+' || *format == ' ' | |
+ || *format == '#' || *format == '0') | |
+ format++; | |
+ | |
+ if (*format == '*') | |
+ { | |
+ format++; | |
+ | |
+ /* Named and unnamed specifications are exclusive. */ | |
+ if (spec.named_arg_count > 0) | |
+ { | |
+ *invalid_reason = INVALID_MIXES_NAMED_UNNAMED (); | |
+ FDI_SET (format - 1, FMTDIR_ERROR); | |
+ goto bad_format; | |
+ } | |
+ | |
+ if (spec.allocated == spec.unnamed_arg_count) | |
+ { | |
+ spec.allocated = 2 * spec.allocated + 1; | |
+ spec.unnamed = (struct unnamed_arg *) xrealloc (spec.unnamed, spec.allocated * sizeof (struct unnamed_arg)); | |
+ } | |
+ spec.unnamed[spec.unnamed_arg_count].type = FAT_INTEGER; | |
+ spec.unnamed_arg_count++; | |
+ } | |
+ else if (isdigit (*format)) | |
+ { | |
+ do format++; while (isdigit (*format)); | |
+ } | |
+ | |
+ if (*format == '.') | |
+ { | |
+ format++; | |
+ | |
+ if (*format == '*') | |
+ { | |
+ format++; | |
+ | |
+ /* Named and unnamed specifications are exclusive. */ | |
+ if (spec.named_arg_count > 0) | |
+ { | |
+ *invalid_reason = INVALID_MIXES_NAMED_UNNAMED (); | |
+ FDI_SET (format - 1, FMTDIR_ERROR); | |
+ goto bad_format; | |
+ } | |
+ | |
+ if (spec.allocated == spec.unnamed_arg_count) | |
+ { | |
+ spec.allocated = 2 * spec.allocated + 1; | |
+ spec.unnamed = (struct unnamed_arg *) xrealloc (spec.unnamed, spec.allocated * sizeof (struct unnamed_arg)); | |
+ } | |
+ spec.unnamed[spec.unnamed_arg_count].type = FAT_INTEGER; | |
+ spec.unnamed_arg_count++; | |
+ } | |
+ else if (isdigit (*format)) | |
+ { | |
+ zero_precision = true; | |
+ do | |
+ { | |
+ if (*format != '0') | |
+ zero_precision = false; | |
+ format++; | |
+ } | |
+ while (isdigit (*format)); | |
+ } | |
+ } | |
+ | |
+ if (*format == 'h' || *format == 'l' || *format == 'L') | |
+ format++; | |
+ | |
+ switch (*format) | |
+ { | |
+ case '%': | |
+ type = FAT_NONE; | |
+ break; | |
+ case 'c': | |
+ type = FAT_CHARACTER; | |
+ break; | |
+ case 's': case 'r': | |
+ type = (zero_precision ? FAT_ANY : FAT_STRING); | |
+ break; | |
+ case 'i': case 'd': case 'u': case 'o': case 'x': case 'X': | |
+ type = FAT_INTEGER; | |
+ break; | |
+ case 'e': case 'E': case 'f': case 'g': case 'G': | |
+ type = FAT_FLOAT; | |
+ break; | |
+ default: | |
+ if (*format == '\0') | |
+ { | |
+ *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); | |
+ FDI_SET (format - 1, FMTDIR_ERROR); | |
+ } | |
+ else | |
+ { | |
+ *invalid_reason = | |
+ INVALID_CONVERSION_SPECIFIER (spec.directives, *format); | |
+ FDI_SET (format, FMTDIR_ERROR); | |
+ } | |
+ goto bad_format; | |
+ } | |
+ | |
+ if (name != NULL) | |
+ { | |
+ /* Named argument. */ | |
+ | |
+ /* Named and unnamed specifications are exclusive. */ | |
+ if (spec.unnamed_arg_count > 0) | |
+ { | |
+ *invalid_reason = INVALID_MIXES_NAMED_UNNAMED (); | |
+ FDI_SET (format, FMTDIR_ERROR); | |
+ goto bad_format; | |
+ } | |
+ | |
+ if (spec.allocated == spec.named_arg_count) | |
+ { | |
+ spec.allocated = 2 * spec.allocated + 1; | |
+ spec.named = (struct named_arg *) xrealloc (spec.named, spec.allocated * sizeof (struct named_arg)); | |
+ } | |
+ spec.named[spec.named_arg_count].name = name; | |
+ spec.named[spec.named_arg_count].type = type; | |
+ spec.named_arg_count++; | |
+ } | |
+ else if (*format != '%') | |
+ { | |
+ /* Unnamed argument. */ | |
+ | |
+ /* Named and unnamed specifications are exclusive. */ | |
+ if (spec.named_arg_count > 0) | |
+ { | |
+ *invalid_reason = INVALID_MIXES_NAMED_UNNAMED (); | |
+ FDI_SET (format, FMTDIR_ERROR); | |
+ goto bad_format; | |
+ } | |
+ | |
+ if (spec.allocated == spec.unnamed_arg_count) | |
+ { | |
+ spec.allocated = 2 * spec.allocated + 1; | |
+ spec.unnamed = (struct unnamed_arg *) xrealloc (spec.unnamed, spec.allocated * sizeof (struct unnamed_arg)); | |
+ } | |
+ spec.unnamed[spec.unnamed_arg_count].type = type; | |
+ spec.unnamed_arg_count++; | |
+ } | |
+ | |
+ FDI_SET (format, FMTDIR_END); | |
+ | |
+ format++; | |
+ } | |
+ | |
+ /* Sort the named argument array, and eliminate duplicates. */ | |
+ if (spec.named_arg_count > 1) | |
+ { | |
+ unsigned int i, j; | |
+ bool err; | |
+ | |
+ qsort (spec.named, spec.named_arg_count, sizeof (struct named_arg), | |
+ named_arg_compare); | |
+ | |
+ /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */ | |
+ err = false; | |
+ for (i = j = 0; i < spec.named_arg_count; i++) | |
+ if (j > 0 && strcmp (spec.named[i].name, spec.named[j-1].name) == 0) | |
+ { | |
+ enum format_arg_type type1 = spec.named[i].type; | |
+ enum format_arg_type type2 = spec.named[j-1].type; | |
+ enum format_arg_type type_both; | |
+ | |
+ if (type1 == type2 || type2 == FAT_ANY) | |
+ type_both = type1; | |
+ else if (type1 == FAT_ANY) | |
+ type_both = type2; | |
+ else | |
+ { | |
+ /* Incompatible types. */ | |
+ type_both = FAT_NONE; | |
+ if (!err) | |
+ *invalid_reason = | |
+ xasprintf (_("The string refers to the argument named '%s' in incompatible ways."), spec.named[i].name); | |
+ err = true; | |
+ } | |
+ | |
+ spec.named[j-1].type = type_both; | |
+ free (spec.named[i].name); | |
+ } | |
+ else | |
+ { | |
+ if (j < i) | |
+ { | |
+ spec.named[j].name = spec.named[i].name; | |
+ spec.named[j].type = spec.named[i].type; | |
+ } | |
+ j++; | |
+ } | |
+ spec.named_arg_count = j; | |
+ if (err) | |
+ /* *invalid_reason has already been set above. */ | |
+ goto bad_format; | |
+ } | |
+ | |
+ result = XMALLOC (struct spec); | |
+ *result = spec; | |
+ return result; | |
+ | |
+ bad_format: | |
+ if (spec.named != NULL) | |
+ { | |
+ unsigned int i; | |
+ for (i = 0; i < spec.named_arg_count; i++) | |
+ free (spec.named[i].name); | |
+ free (spec.named); | |
+ } | |
+ if (spec.unnamed != NULL) | |
+ free (spec.unnamed); | |
+ return NULL; | |
+} | |
+ | |
+static void | |
+format_free (void *descr) | |
+{ | |
+ struct spec *spec = (struct spec *) descr; | |
+ | |
+ if (spec->named != NULL) | |
+ { | |
+ unsigned int i; | |
+ for (i = 0; i < spec->named_arg_count; i++) | |
+ free (spec->named[i].name); | |
+ free (spec->named); | |
+ } | |
+ if (spec->unnamed != NULL) | |
+ free (spec->unnamed); | |
+ free (spec); | |
+} | |
+ | |
+static int | |
+format_get_number_of_directives (void *descr) | |
+{ | |
+ struct spec *spec = (struct spec *) descr; | |
+ | |
+ return spec->directives; | |
+} | |
+ | |
+static bool | |
+format_check (void *msgid_descr, void *msgstr_descr, bool equality, | |
+ formatstring_error_logger_t error_logger, | |
+ const char *pretty_msgid, const char *pretty_msgstr) | |
+{ | |
+ struct spec *spec1 = (struct spec *) msgid_descr; | |
+ struct spec *spec2 = (struct spec *) msgstr_descr; | |
+ bool err = false; | |
+ | |
+ if (spec1->named_arg_count > 0 && spec2->unnamed_arg_count > 0) | |
+ { | |
+ if (error_logger) | |
+ error_logger (_("format specifications in '%s' expect a mapping, those in '%s' expect a tuple"), | |
+ pretty_msgid, pretty_msgstr); | |
+ err = true; | |
+ } | |
+ else if (spec1->unnamed_arg_count > 0 && spec2->named_arg_count > 0) | |
+ { | |
+ if (error_logger) | |
+ error_logger (_("format specifications in '%s' expect a tuple, those in '%s' expect a mapping"), | |
+ pretty_msgid, pretty_msgstr); | |
+ err = true; | |
+ } | |
+ else | |
+ { | |
+ if (spec1->named_arg_count + spec2->named_arg_count > 0) | |
+ { | |
+ unsigned int i, j; | |
+ unsigned int n1 = spec1->named_arg_count; | |
+ unsigned int n2 = spec2->named_arg_count; | |
+ | |
+ /* Check the argument names are the same. | |
+ Both arrays are sorted. We search for the first difference. */ | |
+ for (i = 0, j = 0; i < n1 || j < n2; ) | |
+ { | |
+ int cmp = (i >= n1 ? 1 : | |
+ j >= n2 ? -1 : | |
+ strcmp (spec1->named[i].name, spec2->named[j].name)); | |
+ | |
+ if (cmp > 0) | |
+ { | |
+ if (error_logger) | |
+ error_logger (_("a format specification for argument '%s', as in '%s', doesn't exist in '%s'"), | |
+ spec2->named[j].name, pretty_msgstr, | |
+ pretty_msgid); | |
+ err = true; | |
+ break; | |
+ } | |
+ else if (cmp < 0) | |
+ { | |
+ if (equality) | |
+ { | |
+ if (error_logger) | |
+ error_logger (_("a format specification for argument '%s' doesn't exist in '%s'"), | |
+ spec1->named[i].name, pretty_msgstr); | |
+ err = true; | |
+ break; | |
+ } | |
+ else | |
+ i++; | |
+ } | |
+ else | |
+ j++, i++; | |
+ } | |
+ /* Check the argument types are the same. */ | |
+ if (!err) | |
+ for (i = 0, j = 0; j < n2; ) | |
+ { | |
+ if (strcmp (spec1->named[i].name, spec2->named[j].name) == 0) | |
+ { | |
+ if (!(spec1->named[i].type == spec2->named[j].type | |
+ || (!equality | |
+ && (spec1->named[i].type == FAT_ANY | |
+ || spec2->named[j].type == FAT_ANY)))) | |
+ { | |
+ if (error_logger) | |
+ error_logger (_("format specifications in '%s' and '%s' for argument '%s' are not the same"), | |
+ pretty_msgid, pretty_msgstr, | |
+ spec2->named[j].name); | |
+ err = true; | |
+ break; | |
+ } | |
+ j++, i++; | |
+ } | |
+ else | |
+ i++; | |
+ } | |
+ } | |
+ | |
+ if (spec1->unnamed_arg_count + spec2->unnamed_arg_count > 0) | |
+ { | |
+ unsigned int i; | |
+ | |
+ /* Check the argument types are the same. */ | |
+ if (spec1->unnamed_arg_count != spec2->unnamed_arg_count) | |
+ { | |
+ if (error_logger) | |
+ error_logger (_("number of format specifications in '%s' and '%s' does not match"), | |
+ pretty_msgid, pretty_msgstr); | |
+ err = true; | |
+ } | |
+ else | |
+ for (i = 0; i < spec2->unnamed_arg_count; i++) | |
+ if (!(spec1->unnamed[i].type == spec2->unnamed[i].type | |
+ || (!equality | |
+ && (spec1->unnamed[i].type == FAT_ANY | |
+ || spec2->unnamed[i].type == FAT_ANY)))) | |
+ { | |
+ if (error_logger) | |
+ error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"), | |
+ pretty_msgid, pretty_msgstr, i + 1); | |
+ err = true; | |
+ } | |
+ } | |
+ } | |
+ | |
+ return err; | |
+} | |
+ | |
+ | |
+struct formatstring_parser formatstring_javascript = | |
+{ | |
+ format_parse, | |
+ format_free, | |
+ format_get_number_of_directives, | |
+ NULL, | |
+ format_check | |
+}; | |
+ | |
+ | |
+unsigned int | |
+get_javascript_format_unnamed_arg_count (const char *string) | |
+{ | |
+ /* Parse the format string. */ | |
+ char *invalid_reason = NULL; | |
+ struct spec *descr = | |
+ (struct spec *) format_parse (string, false, NULL, &invalid_reason); | |
+ | |
+ if (descr != NULL) | |
+ { | |
+ unsigned int result = descr->unnamed_arg_count; | |
+ | |
+ format_free (descr); | |
+ return result; | |
+ } | |
+ else | |
+ { | |
+ free (invalid_reason); | |
+ return 0; | |
+ } | |
+} | |
+ | |
+ | |
+#ifdef TEST | |
+ | |
+/* Test program: Print the argument list specification returned by | |
+ format_parse for strings read from standard input. */ | |
+ | |
+#include <stdio.h> | |
+ | |
+static void | |
+format_print (void *descr) | |
+{ | |
+ struct spec *spec = (struct spec *) descr; | |
+ unsigned int i; | |
+ | |
+ if (spec == NULL) | |
+ { | |
+ printf ("INVALID"); | |
+ return; | |
+ } | |
+ | |
+ if (spec->named_arg_count > 0) | |
+ { | |
+ if (spec->unnamed_arg_count > 0) | |
+ abort (); | |
+ | |
+ printf ("{"); | |
+ for (i = 0; i < spec->named_arg_count; i++) | |
+ { | |
+ if (i > 0) | |
+ printf (", "); | |
+ printf ("'%s':", spec->named[i].name); | |
+ switch (spec->named[i].type) | |
+ { | |
+ case FAT_ANY: | |
+ printf ("*"); | |
+ break; | |
+ case FAT_CHARACTER: | |
+ printf ("c"); | |
+ break; | |
+ case FAT_STRING: | |
+ printf ("s"); | |
+ break; | |
+ case FAT_INTEGER: | |
+ printf ("i"); | |
+ break; | |
+ case FAT_FLOAT: | |
+ printf ("f"); | |
+ break; | |
+ default: | |
+ abort (); | |
+ } | |
+ } | |
+ printf ("}"); | |
+ } | |
+ else | |
+ { | |
+ printf ("("); | |
+ for (i = 0; i < spec->unnamed_arg_count; i++) | |
+ { | |
+ if (i > 0) | |
+ printf (" "); | |
+ switch (spec->unnamed[i].type) | |
+ { | |
+ case FAT_ANY: | |
+ printf ("*"); | |
+ break; | |
+ case FAT_CHARACTER: | |
+ printf ("c"); | |
+ break; | |
+ case FAT_STRING: | |
+ printf ("s"); | |
+ break; | |
+ case FAT_INTEGER: | |
+ printf ("i"); | |
+ break; | |
+ case FAT_FLOAT: | |
+ printf ("f"); | |
+ break; | |
+ default: | |
+ abort (); | |
+ } | |
+ } | |
+ printf (")"); | |
+ } | |
+} | |
+ | |
+int | |
+main () | |
+{ | |
+ for (;;) | |
+ { | |
+ char *line = NULL; | |
+ size_t line_size = 0; | |
+ int line_len; | |
+ char *invalid_reason; | |
+ void *descr; | |
+ | |
+ line_len = getline (&line, &line_size, stdin); | |
+ if (line_len < 0) | |
+ break; | |
+ if (line_len > 0 && line[line_len - 1] == '\n') | |
+ line[--line_len] = '\0'; | |
+ | |
+ invalid_reason = NULL; | |
+ descr = format_parse (line, false, NULL, &invalid_reason); | |
+ | |
+ format_print (descr); | |
+ printf ("\n"); | |
+ if (descr == NULL) | |
+ printf ("%s\n", invalid_reason); | |
+ | |
+ free (invalid_reason); | |
+ free (line); | |
+ } | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * For Emacs M-x compile | |
+ * Local Variables: | |
+ * compile-command: "/bin/sh ../libtool --tag=CC --mode=link gcc -o a.out -static -O -g -Wall -I.. -I../gnulib-lib -I../intl -DHAVE_CONFIG_H -DTEST format-javascript.c ../gnulib-lib/libgettextlib.la" | |
+ * End: | |
+ */ | |
+ | |
+#endif /* TEST */ | |
diff --git c/gettext-tools/src/format.c i/gettext-tools/src/format.c | |
index e6c5de9..22fc8c2 100644 | |
--- c/gettext-tools/src/format.c | |
+++ i/gettext-tools/src/format.c | |
@@ -44,6 +44,7 @@ struct formatstring_parser *formatstring_parsers[NFORMATS] = | |
/* format_scheme */ &formatstring_scheme, | |
/* format_smalltalk */ &formatstring_smalltalk, | |
/* format_java */ &formatstring_java, | |
+ /* format_javascript */ &formatstring_javascript, | |
/* format_csharp */ &formatstring_csharp, | |
/* format_awk */ &formatstring_awk, | |
/* format_pascal */ &formatstring_pascal, | |
diff --git c/gettext-tools/src/format.h i/gettext-tools/src/format.h | |
index 60f0adc..679cd9a 100644 | |
--- c/gettext-tools/src/format.h | |
+++ i/gettext-tools/src/format.h | |
@@ -105,6 +105,7 @@ extern DLL_VARIABLE struct formatstring_parser formatstring_librep; | |
extern DLL_VARIABLE struct formatstring_parser formatstring_scheme; | |
extern DLL_VARIABLE struct formatstring_parser formatstring_smalltalk; | |
extern DLL_VARIABLE struct formatstring_parser formatstring_java; | |
+extern DLL_VARIABLE struct formatstring_parser formatstring_javascript; | |
extern DLL_VARIABLE struct formatstring_parser formatstring_csharp; | |
extern DLL_VARIABLE struct formatstring_parser formatstring_awk; | |
extern DLL_VARIABLE struct formatstring_parser formatstring_pascal; | |
diff --git c/gettext-tools/src/message.c i/gettext-tools/src/message.c | |
index 5162b06..e57ddab 100644 | |
--- c/gettext-tools/src/message.c | |
+++ i/gettext-tools/src/message.c | |
@@ -44,6 +44,7 @@ const char *const format_language[NFORMATS] = | |
/* format_scheme */ "scheme", | |
/* format_smalltalk */ "smalltalk", | |
/* format_java */ "java", | |
+ /* format_javascript */ "javascript", | |
/* format_csharp */ "csharp", | |
/* format_awk */ "awk", | |
/* format_pascal */ "object-pascal", | |
@@ -72,6 +73,7 @@ const char *const format_language_pretty[NFORMATS] = | |
/* format_scheme */ "Scheme", | |
/* format_smalltalk */ "Smalltalk", | |
/* format_java */ "Java", | |
+ /* format_javascript */ "JavaScript", | |
/* format_csharp */ "C#", | |
/* format_awk */ "awk", | |
/* format_pascal */ "Object Pascal", | |
diff --git c/gettext-tools/src/message.h i/gettext-tools/src/message.h | |
index af9244a..0f200b1 100644 | |
--- c/gettext-tools/src/message.h | |
+++ i/gettext-tools/src/message.h | |
@@ -53,6 +53,7 @@ enum format_type | |
format_scheme, | |
format_smalltalk, | |
format_java, | |
+ format_javascript, | |
format_csharp, | |
format_awk, | |
format_pascal, | |
@@ -68,7 +69,7 @@ enum format_type | |
format_kde, | |
format_boost | |
}; | |
-#define NFORMATS 24 /* Number of format_type enum values. */ | |
+#define NFORMATS 25 /* Number of format_type enum values. */ | |
extern DLL_VARIABLE const char *const format_language[NFORMATS]; | |
extern DLL_VARIABLE const char *const format_language_pretty[NFORMATS]; | |
diff --git c/gettext-tools/src/x-javascript.c i/gettext-tools/src/x-javascript.c | |
new file mode 100644 | |
index 0000000..f97308c | |
--- /dev/null | |
+++ i/gettext-tools/src/x-javascript.c | |
@@ -0,0 +1,1809 @@ | |
+/* xgettext JavaScript backend. | |
+ Copyright (C) 2002-2003, 2005-2009 Free Software Foundation, Inc. | |
+ | |
+ This file was written by Andreas Stricker <[email protected]>, 2010 | |
+ It's based on x-python from Bruno Haible. | |
+ | |
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>. */ | |
+ | |
+#ifdef HAVE_CONFIG_H | |
+# include "config.h" | |
+#endif | |
+ | |
+/* Specification. */ | |
+#include "x-javascript.h" | |
+ | |
+#include <assert.h> | |
+#include <errno.h> | |
+#include <stdbool.h> | |
+#include <stdio.h> | |
+#include <stdlib.h> | |
+#include <string.h> | |
+ | |
+#include "message.h" | |
+#include "xgettext.h" | |
+#include "error.h" | |
+#include "error-progname.h" | |
+#include "progname.h" | |
+#include "basename.h" | |
+#include "xerror.h" | |
+#include "xvasprintf.h" | |
+#include "xalloc.h" | |
+#include "c-strstr.h" | |
+#include "c-ctype.h" | |
+#include "po-charset.h" | |
+#include "uniname.h" | |
+#include "unistr.h" | |
+#include "gettext.h" | |
+ | |
+#define _(s) gettext(s) | |
+ | |
+#define max(a,b) ((a) > (b) ? (a) : (b)) | |
+ | |
+#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) | |
+ | |
+/* The JavaScript aka ECMA-Script syntax is defined in ECMA-262 | |
+ specification: | |
+ http://www.ecma-international.org/publications/standards/Ecma-262.htm */ | |
+ | |
+/* ====================== Keyword set customization. ====================== */ | |
+ | |
+/* If true extract all strings. */ | |
+static bool extract_all = false; | |
+ | |
+static hash_table keywords; | |
+static bool default_keywords = true; | |
+ | |
+ | |
+void | |
+x_javascript_extract_all () | |
+{ | |
+ extract_all = true; | |
+} | |
+ | |
+ | |
+void | |
+x_javascript_keyword (const char *name) | |
+{ | |
+ if (name == NULL) | |
+ default_keywords = false; | |
+ else | |
+ { | |
+ const char *end; | |
+ struct callshape shape; | |
+ const char *colon; | |
+ | |
+ if (keywords.table == NULL) | |
+ hash_init (&keywords, 100); | |
+ | |
+ split_keywordspec (name, &end, &shape); | |
+ | |
+ /* The characters between name and end should form a valid C identifier. | |
+ A colon means an invalid parse in split_keywordspec(). */ | |
+ colon = strchr (name, ':'); | |
+ if (colon == NULL || colon >= end) | |
+ insert_keyword_callshape (&keywords, name, end - name, &shape); | |
+ } | |
+} | |
+ | |
+/* Finish initializing the keywords hash table. | |
+ Called after argument processing, before each file is processed. */ | |
+static void | |
+init_keywords () | |
+{ | |
+ if (default_keywords) | |
+ { | |
+ /* When adding new keywords here, also update the documentation in | |
+ xgettext.texi! */ | |
+ x_javascript_keyword ("gettext"); | |
+ x_javascript_keyword ("ugettext"); | |
+ x_javascript_keyword ("dgettext:2"); | |
+ x_javascript_keyword ("ngettext:1,2"); | |
+ x_javascript_keyword ("ungettext:1,2"); | |
+ x_javascript_keyword ("dngettext:2,3"); | |
+ x_javascript_keyword ("_"); | |
+ default_keywords = false; | |
+ } | |
+} | |
+ | |
+void | |
+init_flag_table_javascript () | |
+{ | |
+ xgettext_record_flag ("gettext:1:pass-javascript-format"); | |
+ xgettext_record_flag ("ugettext:1:pass-javascript-format"); | |
+ xgettext_record_flag ("dgettext:2:pass-javascript-format"); | |
+ xgettext_record_flag ("ngettext:1:pass-javascript-format"); | |
+ xgettext_record_flag ("ngettext:2:pass-javascript-format"); | |
+ xgettext_record_flag ("ungettext:1:pass-javascript-format"); | |
+ xgettext_record_flag ("ungettext:2:pass-javascript-format"); | |
+ xgettext_record_flag ("dngettext:2:pass-javascript-format"); | |
+ xgettext_record_flag ("dngettext:3:pass-javascript-format"); | |
+ xgettext_record_flag ("_:1:pass-javascript-format"); | |
+} | |
+ | |
+ | |
+/* ======================== Reading of characters. ======================== */ | |
+ | |
+/* Real filename, used in error messages about the input file. */ | |
+static const char *real_file_name; | |
+ | |
+/* Logical filename and line number, used to label the extracted messages. */ | |
+static char *logical_file_name; | |
+static int line_number; | |
+ | |
+/* The input file stream. */ | |
+static FILE *fp; | |
+ | |
+ | |
+/* 1. line_number handling. */ | |
+ | |
+/* Maximum used, roughly a safer MB_LEN_MAX. */ | |
+#define MAX_PHASE1_PUSHBACK 16 | |
+static unsigned char phase1_pushback[MAX_PHASE1_PUSHBACK]; | |
+static int phase1_pushback_length; | |
+ | |
+/* Read the next single byte from the input file. */ | |
+static int | |
+phase1_getc () | |
+{ | |
+ int c; | |
+ | |
+ if (phase1_pushback_length) | |
+ c = phase1_pushback[--phase1_pushback_length]; | |
+ else | |
+ { | |
+ c = getc (fp); | |
+ | |
+ if (c == EOF) | |
+ { | |
+ if (ferror (fp)) | |
+ error (EXIT_FAILURE, errno, _("error while reading \"%s\""), | |
+ real_file_name); | |
+ return EOF; | |
+ } | |
+ } | |
+ | |
+ if (c == '\n') | |
+ ++line_number; | |
+ | |
+ return c; | |
+} | |
+ | |
+/* Supports MAX_PHASE1_PUSHBACK characters of pushback. */ | |
+static void | |
+phase1_ungetc (int c) | |
+{ | |
+ if (c != EOF) | |
+ { | |
+ if (c == '\n') | |
+ --line_number; | |
+ | |
+ if (phase1_pushback_length == SIZEOF (phase1_pushback)) | |
+ abort (); | |
+ phase1_pushback[phase1_pushback_length++] = c; | |
+ } | |
+} | |
+ | |
+ | |
+/* Phase 2: Conversion to Unicode. | |
+ For now, we expect Javascript files to be encoded as UTF-8 */ | |
+ | |
+/* End-of-file indicator for functions returning an UCS-4 character. */ | |
+#define UEOF -1 | |
+ | |
+static lexical_context_ty lexical_context; | |
+ | |
+static int phase2_pushback[max (9, UNINAME_MAX + 3)]; | |
+static int phase2_pushback_length; | |
+ | |
+/* Read the next Unicode UCS-4 character from the input file. */ | |
+static int | |
+phase2_getc () | |
+{ | |
+ if (phase2_pushback_length) | |
+ return phase2_pushback[--phase2_pushback_length]; | |
+ | |
+ if (xgettext_current_source_encoding == po_charset_ascii) | |
+ { | |
+ int c = phase1_getc (); | |
+ if (c == EOF) | |
+ return UEOF; | |
+ if (!c_isascii (c)) | |
+ { | |
+ multiline_error (xstrdup (""), | |
+ xasprintf ("%s\n%s\n", | |
+ non_ascii_error_message (lexical_context, | |
+ real_file_name, | |
+ line_number), | |
+ _("\ | |
+Please specify the source encoding through --from-code\n"))); | |
+ exit (EXIT_FAILURE); | |
+ } | |
+ return c; | |
+ } | |
+ else if (xgettext_current_source_encoding != po_charset_utf8) | |
+ { | |
+#if HAVE_ICONV | |
+ /* Use iconv on an increasing number of bytes. Read only as many bytes | |
+ through phase1_getc as needed. This is needed to give reasonable | |
+ interactive behaviour when fp is connected to an interactive tty. */ | |
+ unsigned char buf[MAX_PHASE1_PUSHBACK]; | |
+ size_t bufcount; | |
+ int c = phase1_getc (); | |
+ if (c == EOF) | |
+ return UEOF; | |
+ buf[0] = (unsigned char) c; | |
+ bufcount = 1; | |
+ | |
+ for (;;) | |
+ { | |
+ unsigned char scratchbuf[6]; | |
+ const char *inptr = (const char *) &buf[0]; | |
+ size_t insize = bufcount; | |
+ char *outptr = (char *) &scratchbuf[0]; | |
+ size_t outsize = sizeof (scratchbuf); | |
+ | |
+ size_t res = iconv (xgettext_current_source_iconv, | |
+ (ICONV_CONST char **) &inptr, &insize, | |
+ &outptr, &outsize); | |
+ /* We expect that a character has been produced if and only if | |
+ some input bytes have been consumed. */ | |
+ if ((insize < bufcount) != (outsize < sizeof (scratchbuf))) | |
+ abort (); | |
+ if (outsize == sizeof (scratchbuf)) | |
+ { | |
+ /* No character has been produced. Must be an error. */ | |
+ if (res != (size_t)(-1)) | |
+ abort (); | |
+ | |
+ if (errno == EILSEQ) | |
+ { | |
+ /* An invalid multibyte sequence was encountered. */ | |
+ multiline_error (xstrdup (""), | |
+ xasprintf (_("\ | |
+%s:%d: Invalid multibyte sequence.\n\ | |
+Please specify the correct source encoding through --from-code\n"), | |
+ real_file_name, line_number)); | |
+ exit (EXIT_FAILURE); | |
+ } | |
+ else if (errno == EINVAL) | |
+ { | |
+ /* An incomplete multibyte character. */ | |
+ int c; | |
+ | |
+ if (bufcount == MAX_PHASE1_PUSHBACK) | |
+ { | |
+ /* An overlong incomplete multibyte sequence was | |
+ encountered. */ | |
+ multiline_error (xstrdup (""), | |
+ xasprintf (_("\ | |
+%s:%d: Long incomplete multibyte sequence.\n\ | |
+Please specify the correct source encoding through --from-code\n"), | |
+ real_file_name, line_number)); | |
+ exit (EXIT_FAILURE); | |
+ } | |
+ | |
+ /* Read one more byte and retry iconv. */ | |
+ c = phase1_getc (); | |
+ if (c == EOF) | |
+ { | |
+ multiline_error (xstrdup (""), | |
+ xasprintf (_("\ | |
+%s:%d: Incomplete multibyte sequence at end of file.\n\ | |
+Please specify the correct source encoding through --from-code\n"), | |
+ real_file_name, line_number)); | |
+ exit (EXIT_FAILURE); | |
+ } | |
+ if (c == '\n') | |
+ { | |
+ multiline_error (xstrdup (""), | |
+ xasprintf (_("\ | |
+%s:%d: Incomplete multibyte sequence at end of line.\n\ | |
+Please specify the correct source encoding through --from-code\n"), | |
+ real_file_name, line_number - 1)); | |
+ exit (EXIT_FAILURE); | |
+ } | |
+ buf[bufcount++] = (unsigned char) c; | |
+ } | |
+ else | |
+ error (EXIT_FAILURE, errno, _("%s:%d: iconv failure"), | |
+ real_file_name, line_number); | |
+ } | |
+ else | |
+ { | |
+ size_t outbytes = sizeof (scratchbuf) - outsize; | |
+ size_t bytes = bufcount - insize; | |
+ ucs4_t uc; | |
+ | |
+ /* We expect that one character has been produced. */ | |
+ if (bytes == 0) | |
+ abort (); | |
+ if (outbytes == 0) | |
+ abort (); | |
+ /* Push back the unused bytes. */ | |
+ while (insize > 0) | |
+ phase1_ungetc (buf[--insize]); | |
+ /* Convert the character from UTF-8 to UCS-4. */ | |
+ if (u8_mbtouc (&uc, scratchbuf, outbytes) < outbytes) | |
+ { | |
+ /* scratchbuf contains an out-of-range Unicode character | |
+ (> 0x10ffff). */ | |
+ multiline_error (xstrdup (""), | |
+ xasprintf (_("\ | |
+%s:%d: Invalid multibyte sequence.\n\ | |
+Please specify the source encoding through --from-code\n"), | |
+ real_file_name, line_number)); | |
+ exit (EXIT_FAILURE); | |
+ } | |
+ return uc; | |
+ } | |
+ } | |
+#else | |
+ /* If we don't have iconv(), the only supported values for | |
+ xgettext_global_source_encoding and thus also for | |
+ xgettext_current_source_encoding are ASCII and UTF-8. */ | |
+ abort (); | |
+#endif | |
+ } | |
+ else | |
+ { | |
+ /* Read an UTF-8 encoded character. */ | |
+ unsigned char buf[6]; | |
+ unsigned int count; | |
+ int c; | |
+ ucs4_t uc; | |
+ | |
+ c = phase1_getc (); | |
+ if (c == EOF) | |
+ return UEOF; | |
+ buf[0] = c; | |
+ count = 1; | |
+ | |
+ if (buf[0] >= 0xc0) | |
+ { | |
+ c = phase1_getc (); | |
+ if (c == EOF) | |
+ return UEOF; | |
+ buf[1] = c; | |
+ count = 2; | |
+ } | |
+ | |
+ if (buf[0] >= 0xe0 | |
+ && ((buf[1] ^ 0x80) < 0x40)) | |
+ { | |
+ c = phase1_getc (); | |
+ if (c == EOF) | |
+ return UEOF; | |
+ buf[2] = c; | |
+ count = 3; | |
+ } | |
+ | |
+ if (buf[0] >= 0xf0 | |
+ && ((buf[1] ^ 0x80) < 0x40) | |
+ && ((buf[2] ^ 0x80) < 0x40)) | |
+ { | |
+ c = phase1_getc (); | |
+ if (c == EOF) | |
+ return UEOF; | |
+ buf[3] = c; | |
+ count = 4; | |
+ } | |
+ | |
+ if (buf[0] >= 0xf8 | |
+ && ((buf[1] ^ 0x80) < 0x40) | |
+ && ((buf[2] ^ 0x80) < 0x40) | |
+ && ((buf[3] ^ 0x80) < 0x40)) | |
+ { | |
+ c = phase1_getc (); | |
+ if (c == EOF) | |
+ return UEOF; | |
+ buf[4] = c; | |
+ count = 5; | |
+ } | |
+ | |
+ if (buf[0] >= 0xfc | |
+ && ((buf[1] ^ 0x80) < 0x40) | |
+ && ((buf[2] ^ 0x80) < 0x40) | |
+ && ((buf[3] ^ 0x80) < 0x40) | |
+ && ((buf[4] ^ 0x80) < 0x40)) | |
+ { | |
+ c = phase1_getc (); | |
+ if (c == EOF) | |
+ return UEOF; | |
+ buf[5] = c; | |
+ count = 6; | |
+ } | |
+ | |
+ u8_mbtouc (&uc, buf, count); | |
+ return uc; | |
+ } | |
+} | |
+ | |
+/* Supports max (9, UNINAME_MAX + 3) pushback characters. */ | |
+static void | |
+phase2_ungetc (int c) | |
+{ | |
+ if (c != UEOF) | |
+ { | |
+ if (phase2_pushback_length == SIZEOF (phase2_pushback)) | |
+ abort (); | |
+ phase2_pushback[phase2_pushback_length++] = c; | |
+ } | |
+} | |
+ | |
+ | |
+/* ========================= Accumulating strings. ======================== */ | |
+ | |
+/* A string buffer type that allows appending Unicode characters. | |
+ Returns the entire string in UTF-8 encoding. */ | |
+ | |
+struct unicode_string_buffer | |
+{ | |
+ /* The part of the string that has already been converted to UTF-8. */ | |
+ char *utf8_buffer; | |
+ size_t utf8_buflen; | |
+ size_t utf8_allocated; | |
+}; | |
+ | |
+/* Initialize a 'struct unicode_string_buffer' to empty. */ | |
+static inline void | |
+init_unicode_string_buffer (struct unicode_string_buffer *bp) | |
+{ | |
+ bp->utf8_buffer = NULL; | |
+ bp->utf8_buflen = 0; | |
+ bp->utf8_allocated = 0; | |
+} | |
+ | |
+/* Auxiliary function: Ensure count more bytes are available in bp->utf8. */ | |
+static inline void | |
+unicode_string_buffer_append_unicode_grow (struct unicode_string_buffer *bp, | |
+ size_t count) | |
+{ | |
+ if (bp->utf8_buflen + count > bp->utf8_allocated) | |
+ { | |
+ size_t new_allocated = 2 * bp->utf8_allocated + 10; | |
+ if (new_allocated < bp->utf8_buflen + count) | |
+ new_allocated = bp->utf8_buflen + count; | |
+ bp->utf8_allocated = new_allocated; | |
+ bp->utf8_buffer = xrealloc (bp->utf8_buffer, new_allocated); | |
+ } | |
+} | |
+ | |
+/* Auxiliary function: Append a Unicode character to bp->utf8. | |
+ uc must be < 0x110000. */ | |
+static inline void | |
+unicode_string_buffer_append_unicode (struct unicode_string_buffer *bp, | |
+ unsigned int uc) | |
+{ | |
+ unsigned char utf8buf[6]; | |
+ int count = u8_uctomb (utf8buf, uc, 6); | |
+ | |
+ if (count < 0) | |
+ /* The caller should have ensured that uc is not out-of-range. */ | |
+ abort (); | |
+ | |
+ unicode_string_buffer_append_unicode_grow (bp, count); | |
+ memcpy (bp->utf8_buffer + bp->utf8_buflen, utf8buf, count); | |
+ bp->utf8_buflen += count; | |
+} | |
+ | |
+/* Return the string buffer's contents. */ | |
+static char * | |
+unicode_string_buffer_result (struct unicode_string_buffer *bp) | |
+{ | |
+ /* NUL-terminate it. */ | |
+ unicode_string_buffer_append_unicode_grow (bp, 1); | |
+ bp->utf8_buffer[bp->utf8_buflen] = '\0'; | |
+ /* Return it. */ | |
+ return bp->utf8_buffer; | |
+} | |
+ | |
+/* Free the memory pointed to by a 'struct unicode_string_buffer'. */ | |
+static inline void | |
+free_unicode_string_buffer (struct unicode_string_buffer *bp) | |
+{ | |
+ free (bp->utf8_buffer); | |
+} | |
+ | |
+ | |
+/* ======================== Accumulating comments. ======================== */ | |
+ | |
+ | |
+/* Accumulating a single comment line. */ | |
+ | |
+static struct unicode_string_buffer comment_buffer; | |
+ | |
+static inline void | |
+comment_start () | |
+{ | |
+ lexical_context = lc_comment; | |
+ comment_buffer.utf8_buflen = 0; | |
+} | |
+ | |
+static inline bool | |
+comment_at_start () | |
+{ | |
+ return (comment_buffer.utf8_buflen == 0); | |
+} | |
+ | |
+static inline void | |
+comment_add (int c) | |
+{ | |
+ unicode_string_buffer_append_unicode (&comment_buffer, c); | |
+} | |
+ | |
+static inline const char * | |
+comment_line_end () | |
+{ | |
+ char *buffer = unicode_string_buffer_result (&comment_buffer); | |
+ size_t buflen = strlen (buffer); | |
+ | |
+ while (buflen >= 1 | |
+ && (buffer[buflen - 1] == ' ' || buffer[buflen - 1] == '\t')) | |
+ --buflen; | |
+ buffer[buflen] = '\0'; | |
+ savable_comment_add (buffer); | |
+ lexical_context = lc_outside; | |
+ return buffer; | |
+} | |
+ | |
+ | |
+/* These are for tracking whether comments count as immediately before | |
+ keyword. */ | |
+static int last_comment_line; | |
+static int last_non_comment_line; | |
+ | |
+ | |
+/* ======================== Recognizing comments. ======================== */ | |
+ | |
+ | |
+/* Recognizing the "coding" comment. | |
+ JavaScript provides to different comments: C-Style or C++ style | |
+*/ | |
+ | |
+/* Canonicalized encoding name for the current input file. */ | |
+static const char *xgettext_current_file_source_encoding; | |
+ | |
+#if HAVE_ICONV | |
+/* Converter from xgettext_current_file_source_encoding to UTF-8 (except from | |
+ ASCII or UTF-8, when this conversion is a no-op). */ | |
+static iconv_t xgettext_current_file_source_iconv; | |
+#endif | |
+ | |
+/* Tracking whether the current line is a continuation line or contains a | |
+ non-blank character. */ | |
+static bool continuation_or_nonblank_line = false; | |
+ | |
+/* Phase 3: Outside strings, replace backslash-newline with nothing and a | |
+ comment with nothing. */ | |
+ | |
+static int | |
+phase3_getc () | |
+{ | |
+ int c; | |
+ | |
+ for (;;) | |
+ { | |
+ c = phase2_getc (); | |
+ if (c == '\\') | |
+ { | |
+ c = phase2_getc (); | |
+ if (c != '\n') | |
+ { | |
+ phase2_ungetc (c); | |
+ /* This shouldn't happen usually, because "A backslash is | |
+ illegal elsewhere on a line outside a string literal." */ | |
+ return '\\'; | |
+ } | |
+ /* Eat backslash-newline. */ | |
+ continuation_or_nonblank_line = true; | |
+ } | |
+ else if (c == '/') | |
+ { | |
+ c = phase2_getc (); | |
+ if (c == '/') | |
+ { | |
+ /* Eat a comment to the end of line. */ | |
+ last_comment_line = line_number; | |
+ comment_start (); | |
+ for (;;) | |
+ { | |
+ c = phase2_getc (); | |
+ if (c == UEOF || c == '\n') | |
+ break; | |
+ /* We skip all leading white space, but not EOLs. */ | |
+ if (!(comment_at_start () && (c == ' ' || c == '\t'))) | |
+ comment_add (c); | |
+ } | |
+ continuation_or_nonblank_line = false; | |
+ return c; | |
+ } | |
+ else if (c == '*') | |
+ { | |
+ /* Eat a comment to the end marker. (like this one!) */ | |
+ last_comment_line = line_number; | |
+ comment_start (); | |
+ for (;;) | |
+ { | |
+ c = phase2_getc (); | |
+ if (c == UEOF) | |
+ break; | |
+ if (c == '*') | |
+ { | |
+ c = phase2_getc (); | |
+ if (c == '/') | |
+ break; | |
+ else | |
+ phase2_ungetc (c); | |
+ } | |
+ comment_add (c); | |
+ } | |
+ continuation_or_nonblank_line = false; | |
+ } | |
+ else | |
+ { | |
+ phase2_ungetc (c); | |
+ return '/'; | |
+ } | |
+ } | |
+ else | |
+ { | |
+ if (c == '\n') | |
+ continuation_or_nonblank_line = false; | |
+ else if (!(c == ' ' || c == '\t' || c == '\f')) | |
+ continuation_or_nonblank_line = true; | |
+ return c; | |
+ } | |
+ } | |
+} | |
+ | |
+/* Supports only one pushback character. */ | |
+static void | |
+phase3_ungetc (int c) | |
+{ | |
+ phase2_ungetc (c); | |
+} | |
+ | |
+ | |
+/* ========================= Accumulating strings. ======================== */ | |
+ | |
+/* Return value of phase7_getuc when EOF is reached. */ | |
+#define P7_EOF (-1) | |
+#define P7_STRING_END (-2) | |
+ | |
+/* Convert an UTF-16 or UTF-32 code point to a return value that can be | |
+ distinguished from a single-byte return value. */ | |
+#define UNICODE(code) (0x100 + (code)) | |
+ | |
+/* Test a return value of phase7_getuc whether it designates an UTF-16 or | |
+ UTF-32 code point. */ | |
+#define IS_UNICODE(p7_result) ((p7_result) >= 0x100) | |
+ | |
+/* Extract the UTF-16 or UTF-32 code of a return value that satisfies | |
+ IS_UNICODE. */ | |
+#define UNICODE_VALUE(p7_result) ((p7_result) - 0x100) | |
+ | |
+/* A string buffer type that allows appending bytes (in the | |
+ xgettext_current_source_encoding) or Unicode characters. | |
+ Returns the entire string in UTF-8 encoding. */ | |
+ | |
+struct mixed_string_buffer | |
+{ | |
+ /* The part of the string that has already been converted to UTF-8. */ | |
+ char *utf8_buffer; | |
+ size_t utf8_buflen; | |
+ size_t utf8_allocated; | |
+ /* The first half of an UTF-16 surrogate character. */ | |
+ unsigned short utf16_surr; | |
+ /* The part of the string that is still in the source encoding. */ | |
+ char *curr_buffer; | |
+ size_t curr_buflen; | |
+ size_t curr_allocated; | |
+ /* The lexical context. Used only for error message purposes. */ | |
+ lexical_context_ty lcontext; | |
+}; | |
+ | |
+/* Initialize a 'struct mixed_string_buffer' to empty. */ | |
+static inline void | |
+init_mixed_string_buffer (struct mixed_string_buffer *bp, lexical_context_ty lcontext) | |
+{ | |
+ bp->utf8_buffer = NULL; | |
+ bp->utf8_buflen = 0; | |
+ bp->utf8_allocated = 0; | |
+ bp->utf16_surr = 0; | |
+ bp->curr_buffer = NULL; | |
+ bp->curr_buflen = 0; | |
+ bp->curr_allocated = 0; | |
+ bp->lcontext = lcontext; | |
+} | |
+ | |
+/* Auxiliary function: Append a byte to bp->curr. */ | |
+static inline void | |
+mixed_string_buffer_append_byte (struct mixed_string_buffer *bp, unsigned char c) | |
+{ | |
+ if (bp->curr_buflen == bp->curr_allocated) | |
+ { | |
+ bp->curr_allocated = 2 * bp->curr_allocated + 10; | |
+ bp->curr_buffer = xrealloc (bp->curr_buffer, bp->curr_allocated); | |
+ } | |
+ bp->curr_buffer[bp->curr_buflen++] = c; | |
+} | |
+ | |
+/* Auxiliary function: Ensure count more bytes are available in bp->utf8. */ | |
+static inline void | |
+mixed_string_buffer_append_unicode_grow (struct mixed_string_buffer *bp, size_t count) | |
+{ | |
+ if (bp->utf8_buflen + count > bp->utf8_allocated) | |
+ { | |
+ size_t new_allocated = 2 * bp->utf8_allocated + 10; | |
+ if (new_allocated < bp->utf8_buflen + count) | |
+ new_allocated = bp->utf8_buflen + count; | |
+ bp->utf8_allocated = new_allocated; | |
+ bp->utf8_buffer = xrealloc (bp->utf8_buffer, new_allocated); | |
+ } | |
+} | |
+ | |
+/* Auxiliary function: Append a Unicode character to bp->utf8. | |
+ uc must be < 0x110000. */ | |
+static inline void | |
+mixed_string_buffer_append_unicode (struct mixed_string_buffer *bp, ucs4_t uc) | |
+{ | |
+ unsigned char utf8buf[6]; | |
+ int count = u8_uctomb (utf8buf, uc, 6); | |
+ | |
+ if (count < 0) | |
+ /* The caller should have ensured that uc is not out-of-range. */ | |
+ abort (); | |
+ | |
+ mixed_string_buffer_append_unicode_grow (bp, count); | |
+ memcpy (bp->utf8_buffer + bp->utf8_buflen, utf8buf, count); | |
+ bp->utf8_buflen += count; | |
+} | |
+ | |
+/* Auxiliary function: Flush bp->utf16_surr into bp->utf8_buffer. */ | |
+static inline void | |
+mixed_string_buffer_flush_utf16_surr (struct mixed_string_buffer *bp) | |
+{ | |
+ if (bp->utf16_surr != 0) | |
+ { | |
+ /* A half surrogate is invalid, therefore use U+FFFD instead. */ | |
+ mixed_string_buffer_append_unicode (bp, 0xfffd); | |
+ bp->utf16_surr = 0; | |
+ } | |
+} | |
+ | |
+/* Auxiliary function: Flush bp->curr_buffer into bp->utf8_buffer. */ | |
+static inline void | |
+mixed_string_buffer_flush_curr_buffer (struct mixed_string_buffer *bp, int lineno) | |
+{ | |
+ if (bp->curr_buflen > 0) | |
+ { | |
+ char *curr; | |
+ size_t count; | |
+ | |
+ mixed_string_buffer_append_byte (bp, '\0'); | |
+ | |
+ /* Convert from the source encoding to UTF-8. */ | |
+ curr = from_current_source_encoding (bp->curr_buffer, bp->lcontext, | |
+ logical_file_name, lineno); | |
+ | |
+ /* Append it to bp->utf8_buffer. */ | |
+ count = strlen (curr); | |
+ mixed_string_buffer_append_unicode_grow (bp, count); | |
+ memcpy (bp->utf8_buffer + bp->utf8_buflen, curr, count); | |
+ bp->utf8_buflen += count; | |
+ | |
+ if (curr != bp->curr_buffer) | |
+ free (curr); | |
+ bp->curr_buflen = 0; | |
+ } | |
+} | |
+ | |
+/* Append a character or Unicode character to a 'struct mixed_string_buffer'. */ | |
+static void | |
+mixed_string_buffer_append (struct mixed_string_buffer *bp, int c) | |
+{ | |
+ if (IS_UNICODE (c)) | |
+ { | |
+ /* Append a Unicode character. */ | |
+ | |
+ /* Switch from multibyte character mode to Unicode character mode. */ | |
+ mixed_string_buffer_flush_curr_buffer (bp, line_number); | |
+ | |
+ /* Test whether this character and the previous one form a Unicode | |
+ surrogate character pair. */ | |
+ if (bp->utf16_surr != 0 | |
+ && (c >= UNICODE (0xdc00) && c < UNICODE (0xe000))) | |
+ { | |
+ unsigned short utf16buf[2]; | |
+ ucs4_t uc; | |
+ | |
+ utf16buf[0] = bp->utf16_surr; | |
+ utf16buf[1] = UNICODE_VALUE (c); | |
+ if (u16_mbtouc (&uc, utf16buf, 2) != 2) | |
+ abort (); | |
+ | |
+ mixed_string_buffer_append_unicode (bp, uc); | |
+ bp->utf16_surr = 0; | |
+ } | |
+ else | |
+ { | |
+ mixed_string_buffer_flush_utf16_surr (bp); | |
+ | |
+ if (c >= UNICODE (0xd800) && c < UNICODE (0xdc00)) | |
+ bp->utf16_surr = UNICODE_VALUE (c); | |
+ else if (c >= UNICODE (0xdc00) && c < UNICODE (0xe000)) | |
+ { | |
+ /* A half surrogate is invalid, therefore use U+FFFD instead. */ | |
+ mixed_string_buffer_append_unicode (bp, 0xfffd); | |
+ } | |
+ else | |
+ mixed_string_buffer_append_unicode (bp, UNICODE_VALUE (c)); | |
+ } | |
+ } | |
+ else | |
+ { | |
+ /* Append a single byte. */ | |
+ | |
+ /* Switch from Unicode character mode to multibyte character mode. */ | |
+ mixed_string_buffer_flush_utf16_surr (bp); | |
+ | |
+ /* When a newline is seen, convert the accumulated multibyte sequence. | |
+ This ensures a correct line number in the error message in case of | |
+ a conversion error. The "- 1" is to account for the newline. */ | |
+ if (c == '\n') | |
+ mixed_string_buffer_flush_curr_buffer (bp, line_number - 1); | |
+ | |
+ mixed_string_buffer_append_byte (bp, (unsigned char) c); | |
+ } | |
+} | |
+ | |
+/* Return the string buffer's contents. */ | |
+static char * | |
+mixed_string_buffer_result (struct mixed_string_buffer *bp) | |
+{ | |
+ /* Flush all into bp->utf8_buffer. */ | |
+ mixed_string_buffer_flush_utf16_surr (bp); | |
+ mixed_string_buffer_flush_curr_buffer (bp, line_number); | |
+ /* NUL-terminate it. */ | |
+ mixed_string_buffer_append_unicode_grow (bp, 1); | |
+ bp->utf8_buffer[bp->utf8_buflen] = '\0'; | |
+ /* Return it. */ | |
+ return bp->utf8_buffer; | |
+} | |
+ | |
+/* Free the memory pointed to by a 'struct mixed_string_buffer'. */ | |
+static inline void | |
+free_mixed_string_buffer (struct mixed_string_buffer *bp) | |
+{ | |
+ free (bp->utf8_buffer); | |
+ free (bp->curr_buffer); | |
+} | |
+ | |
+ | |
+/* ========================== Reading of tokens. ========================== */ | |
+ | |
+ | |
+enum token_type_ty | |
+{ | |
+ token_type_eof, | |
+ token_type_lparen, /* ( */ | |
+ token_type_rparen, /* ) */ | |
+ token_type_comma, /* , */ | |
+ token_type_lbracket, /* [ */ | |
+ token_type_rbracket, /* ] */ | |
+ token_type_regexp, /* /.../ */ | |
+ token_type_operator, /* + - * / % . < > = ~ ! | & ? : ^ */ | |
+ token_type_string, /* "abc", 'abc' */ | |
+ token_type_symbol, /* symbol, number */ | |
+ token_type_other /* misc. operator */ | |
+}; | |
+typedef enum token_type_ty token_type_ty; | |
+ | |
+typedef struct token_ty token_ty; | |
+struct token_ty | |
+{ | |
+ token_type_ty type; | |
+ char *string; /* for token_type_string, token_type_symbol */ | |
+ refcounted_string_list_ty *comment; /* for token_type_string */ | |
+ int line_number; | |
+ int operator; /* for token_type_operator */ | |
+}; | |
+ | |
+/* Javascript provides strings with either double or single quotes. | |
+ "abc" or 'abc' | |
+ Both may contain special sequences after backslash: | |
+ \\, \", \', \a\b\f\n\r\t\v | |
+ Special characters from ascii range are selected by on of the following | |
+ octal or hexadecimal sequences: | |
+ \oOO \xXX | |
+ Any unicode point can be entered using the sequence \uNNNN or \uNNNNNNNN | |
+ */ | |
+ | |
+static int | |
+phase7_getuc (int quote_char, | |
+ bool interpret_ansic, bool interpret_unicode, | |
+ unsigned int *backslash_counter) | |
+{ | |
+ int c; | |
+ | |
+ for (;;) | |
+ { | |
+ /* Use phase 2, because phase 3 elides comments. */ | |
+ c = phase2_getc (); | |
+ | |
+ if (c == UEOF) | |
+ return P7_EOF; | |
+ | |
+ if (c == quote_char && (interpret_ansic || (*backslash_counter & 1) == 0)) | |
+ { | |
+ return P7_STRING_END; | |
+ } | |
+ | |
+ if (c == '\n') | |
+ { | |
+ phase2_ungetc (c); | |
+ error_with_progname = false; | |
+ error (0, 0, _("%s:%d: warning: unterminated string"), | |
+ logical_file_name, line_number); | |
+ error_with_progname = true; | |
+ return P7_STRING_END; | |
+ } | |
+ | |
+ if (c != '\\') | |
+ { | |
+ *backslash_counter = 0; | |
+ return UNICODE (c); | |
+ } | |
+ | |
+ /* Backslash handling. */ | |
+ | |
+ if (!interpret_ansic && !interpret_unicode) | |
+ { | |
+ ++*backslash_counter; | |
+ return UNICODE ('\\'); | |
+ } | |
+ | |
+ /* Dispatch according to the character following the backslash. */ | |
+ c = phase2_getc (); | |
+ if (c == UEOF) | |
+ { | |
+ ++*backslash_counter; | |
+ return UNICODE ('\\'); | |
+ } | |
+ | |
+ if (interpret_ansic) | |
+ switch (c) | |
+ { | |
+ case '\n': | |
+ continue; | |
+ case '\\': | |
+ ++*backslash_counter; | |
+ return UNICODE (c); | |
+ case '\'': case '"': | |
+ *backslash_counter = 0; | |
+ return UNICODE (c); | |
+ case 'a': | |
+ *backslash_counter = 0; | |
+ return UNICODE ('\a'); | |
+ case 'b': | |
+ *backslash_counter = 0; | |
+ return UNICODE ('\b'); | |
+ case 'f': | |
+ *backslash_counter = 0; | |
+ return UNICODE ('\f'); | |
+ case 'n': | |
+ *backslash_counter = 0; | |
+ return UNICODE ('\n'); | |
+ case 'r': | |
+ *backslash_counter = 0; | |
+ return UNICODE ('\r'); | |
+ case 't': | |
+ *backslash_counter = 0; | |
+ return UNICODE ('\t'); | |
+ case 'v': | |
+ *backslash_counter = 0; | |
+ return UNICODE ('\v'); | |
+ case '0': case '1': case '2': case '3': case '4': | |
+ case '5': case '6': case '7': | |
+ { | |
+ int n = c - '0'; | |
+ | |
+ c = phase2_getc (); | |
+ if (c != UEOF) | |
+ { | |
+ if (c >= '0' && c <= '7') | |
+ { | |
+ n = (n << 3) + (c - '0'); | |
+ c = phase2_getc (); | |
+ if (c != UEOF) | |
+ { | |
+ if (c >= '0' && c <= '7') | |
+ n = (n << 3) + (c - '0'); | |
+ else | |
+ phase2_ungetc (c); | |
+ } | |
+ } | |
+ else | |
+ phase2_ungetc (c); | |
+ } | |
+ *backslash_counter = 0; | |
+ if (interpret_unicode) | |
+ return UNICODE (n); | |
+ else | |
+ return (unsigned char) n; | |
+ } | |
+ case 'x': | |
+ { | |
+ int c1 = phase2_getc (); | |
+ int n1; | |
+ | |
+ if (c1 >= '0' && c1 <= '9') | |
+ n1 = c1 - '0'; | |
+ else if (c1 >= 'A' && c1 <= 'F') | |
+ n1 = c1 - 'A' + 10; | |
+ else if (c1 >= 'a' && c1 <= 'f') | |
+ n1 = c1 - 'a' + 10; | |
+ else | |
+ n1 = -1; | |
+ | |
+ if (n1 >= 0) | |
+ { | |
+ int c2 = phase2_getc (); | |
+ int n2; | |
+ | |
+ if (c2 >= '0' && c2 <= '9') | |
+ n2 = c2 - '0'; | |
+ else if (c2 >= 'A' && c2 <= 'F') | |
+ n2 = c2 - 'A' + 10; | |
+ else if (c2 >= 'a' && c2 <= 'f') | |
+ n2 = c2 - 'a' + 10; | |
+ else | |
+ n2 = -1; | |
+ | |
+ if (n2 >= 0) | |
+ { | |
+ int n = (n1 << 4) + n2; | |
+ *backslash_counter = 0; | |
+ if (interpret_unicode) | |
+ return UNICODE (n); | |
+ else | |
+ return (unsigned char) n; | |
+ } | |
+ | |
+ phase2_ungetc (c2); | |
+ } | |
+ phase2_ungetc (c1); | |
+ phase2_ungetc (c); | |
+ ++*backslash_counter; | |
+ return UNICODE ('\\'); | |
+ } | |
+ } | |
+ | |
+ if (interpret_unicode) | |
+ { | |
+ if (c == 'u') | |
+ { | |
+ unsigned char buf[4]; | |
+ unsigned int n = 0; | |
+ int i; | |
+ | |
+ for (i = 0; i < 4; i++) | |
+ { | |
+ int c1 = phase2_getc (); | |
+ | |
+ if (c1 >= '0' && c1 <= '9') | |
+ n = (n << 4) + (c1 - '0'); | |
+ else if (c1 >= 'A' && c1 <= 'F') | |
+ n = (n << 4) + (c1 - 'A' + 10); | |
+ else if (c1 >= 'a' && c1 <= 'f') | |
+ n = (n << 4) + (c1 - 'a' + 10); | |
+ else | |
+ { | |
+ phase2_ungetc (c1); | |
+ while (--i >= 0) | |
+ phase2_ungetc (buf[i]); | |
+ phase2_ungetc (c); | |
+ ++*backslash_counter; | |
+ return UNICODE ('\\'); | |
+ } | |
+ | |
+ buf[i] = c1; | |
+ } | |
+ *backslash_counter = 0; | |
+ return UNICODE (n); | |
+ } | |
+ | |
+ if (interpret_ansic) | |
+ { | |
+ if (c == 'U') | |
+ { | |
+ unsigned char buf[8]; | |
+ unsigned int n = 0; | |
+ int i; | |
+ | |
+ for (i = 0; i < 8; i++) | |
+ { | |
+ int c1 = phase2_getc (); | |
+ | |
+ if (c1 >= '0' && c1 <= '9') | |
+ n = (n << 4) + (c1 - '0'); | |
+ else if (c1 >= 'A' && c1 <= 'F') | |
+ n = (n << 4) + (c1 - 'A' + 10); | |
+ else if (c1 >= 'a' && c1 <= 'f') | |
+ n = (n << 4) + (c1 - 'a' + 10); | |
+ else | |
+ { | |
+ phase2_ungetc (c1); | |
+ while (--i >= 0) | |
+ phase2_ungetc (buf[i]); | |
+ phase2_ungetc (c); | |
+ ++*backslash_counter; | |
+ return UNICODE ('\\'); | |
+ } | |
+ | |
+ buf[i] = c1; | |
+ } | |
+ if (n < 0x110000) | |
+ { | |
+ *backslash_counter = 0; | |
+ return UNICODE (n); | |
+ } | |
+ | |
+ error_with_progname = false; | |
+ error (0, 0, _("%s:%d: warning: invalid Unicode character"), | |
+ logical_file_name, line_number); | |
+ error_with_progname = true; | |
+ | |
+ while (--i >= 0) | |
+ phase2_ungetc (buf[i]); | |
+ phase2_ungetc (c); | |
+ ++*backslash_counter; | |
+ return UNICODE ('\\'); | |
+ } | |
+ | |
+ if (c == 'N') | |
+ { | |
+ int c1 = phase2_getc (); | |
+ if (c1 == '{') | |
+ { | |
+ unsigned char buf[UNINAME_MAX + 1]; | |
+ int i; | |
+ unsigned int n; | |
+ | |
+ for (i = 0; i < UNINAME_MAX; i++) | |
+ { | |
+ int c2 = phase2_getc (); | |
+ if (!(c2 >= ' ' && c2 <= '~')) | |
+ { | |
+ phase2_ungetc (c2); | |
+ while (--i >= 0) | |
+ phase2_ungetc (buf[i]); | |
+ phase2_ungetc (c1); | |
+ phase2_ungetc (c); | |
+ ++*backslash_counter; | |
+ return UNICODE ('\\'); | |
+ } | |
+ if (c2 == '}') | |
+ break; | |
+ buf[i] = c2; | |
+ } | |
+ buf[i] = '\0'; | |
+ | |
+ n = unicode_name_character ((char *) buf); | |
+ if (n != UNINAME_INVALID) | |
+ { | |
+ *backslash_counter = 0; | |
+ return UNICODE (n); | |
+ } | |
+ | |
+ phase2_ungetc ('}'); | |
+ while (--i >= 0) | |
+ phase2_ungetc (buf[i]); | |
+ } | |
+ phase2_ungetc (c1); | |
+ phase2_ungetc (c); | |
+ ++*backslash_counter; | |
+ return UNICODE ('\\'); | |
+ } | |
+ } | |
+ } | |
+ | |
+ phase2_ungetc (c); | |
+ ++*backslash_counter; | |
+ return UNICODE ('\\'); | |
+ } | |
+} | |
+ | |
+ | |
+/* Combine characters into tokens. Discard whitespace except newlines at | |
+ the end of logical lines. */ | |
+ | |
+/* Number of pending open parentheses/braces/brackets. */ | |
+static int open_pbb; | |
+ | |
+static token_ty phase5_pushback[2]; | |
+static int phase5_pushback_length; | |
+ | |
+static token_type_ty last_token_type = token_type_other; | |
+ | |
+static void | |
+phase5_scan_regexp () | |
+{ | |
+ int c; | |
+ /* scan for end of RegExp literal ('/') */ | |
+ for (;;) | |
+ { | |
+ c = phase2_getc (); /* must use phase2 as there can't be comments */ | |
+ if (c == '/') | |
+ break; | |
+ switch (c) | |
+ { | |
+ case UEOF: | |
+ error_with_progname = false; | |
+ error (0, 0, _("%s:%d: warning: RegExp literal terminated too early"), | |
+ logical_file_name, line_number); | |
+ error_with_progname = true; | |
+ return; | |
+ | |
+ case '\n': | |
+ error_with_progname = false; | |
+ error (0, 0, _("%s:%d: warning: RegExp literal contains newline"), | |
+ logical_file_name, line_number); | |
+ error_with_progname = true; | |
+ return; | |
+ | |
+ case '\\': | |
+ { | |
+ int c2 = phase2_getc (); | |
+ if (c2 == UEOF) | |
+ { | |
+ error_with_progname = false; | |
+ error (0, 0, _("%s:%d: warning: RegExp literal terminated too early"), | |
+ logical_file_name, line_number); | |
+ error_with_progname = true; | |
+ return; | |
+ } | |
+ } | |
+ break; | |
+ | |
+ default: | |
+ continue; | |
+ } | |
+ } | |
+ /* scan for modifier flags (ECMA-262 5th section 15.10.4.1) */ | |
+ c = phase2_getc (); | |
+ if (c == 'g' || c == 'i' || c == 'm') | |
+ { | |
+ return; | |
+ } | |
+ else | |
+ { | |
+ phase2_ungetc (c); | |
+ } | |
+} | |
+ | |
+static void | |
+phase5_get (token_ty *tp) | |
+{ | |
+ int c; | |
+ | |
+ if (phase5_pushback_length) | |
+ { | |
+ *tp = phase5_pushback[--phase5_pushback_length]; | |
+ last_token_type = tp->type; | |
+ return; | |
+ } | |
+ | |
+ for (;;) | |
+ { | |
+ tp->line_number = line_number; | |
+ c = phase3_getc (); | |
+ | |
+ switch (c) | |
+ { | |
+ case UEOF: | |
+ tp->type = last_token_type = token_type_eof; | |
+ return; | |
+ | |
+ case ' ': | |
+ case '\t': | |
+ case '\f': | |
+ /* Ignore whitespace and comments. */ | |
+ continue; | |
+ | |
+ case '\n': | |
+ if (last_non_comment_line > last_comment_line) | |
+ savable_comment_reset (); | |
+ /* Ignore newline if and only if it is used for implicit line | |
+ joining. */ | |
+ if (open_pbb > 0) | |
+ continue; | |
+ tp->type = last_token_type = token_type_other; | |
+ return; | |
+ } | |
+ | |
+ last_non_comment_line = tp->line_number; | |
+ | |
+ switch (c) | |
+ { | |
+ case '.': | |
+ { | |
+ int c1 = phase3_getc (); | |
+ phase3_ungetc (c1); | |
+ if (!(c1 >= '0' && c1 <= '9')) | |
+ { | |
+ | |
+ tp->type = last_token_type = token_type_other; | |
+ return; | |
+ } | |
+ } | |
+ /* FALLTHROUGH */ | |
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': | |
+ case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': | |
+ case 'M': case 'N': case 'O': case 'P': case 'Q': | |
+ case 'S': case 'T': case 'V': case 'W': case 'X': | |
+ case 'Y': case 'Z': | |
+ case '_': | |
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': | |
+ case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': | |
+ case 'm': case 'n': case 'o': case 'p': case 'q': | |
+ case 's': case 't': case 'v': case 'w': case 'x': | |
+ case 'y': case 'z': | |
+ case '0': case '1': case '2': case '3': case '4': | |
+ case '5': case '6': case '7': case '8': case '9': | |
+ symbol: | |
+ /* Symbol, or part of a number. */ | |
+ { | |
+ static char *buffer; | |
+ static int bufmax; | |
+ int bufpos; | |
+ | |
+ bufpos = 0; | |
+ for (;;) | |
+ { | |
+ if (bufpos >= bufmax) | |
+ { | |
+ bufmax = 2 * bufmax + 10; | |
+ buffer = xrealloc (buffer, bufmax); | |
+ } | |
+ buffer[bufpos++] = c; | |
+ c = phase3_getc (); | |
+ switch (c) | |
+ { | |
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': | |
+ case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': | |
+ case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': | |
+ case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': | |
+ case 'Y': case 'Z': | |
+ case '_': | |
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': | |
+ case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': | |
+ case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': | |
+ case 's': case 't': case 'u': case 'v': case 'w': case 'x': | |
+ case 'y': case 'z': | |
+ case '0': case '1': case '2': case '3': case '4': | |
+ case '5': case '6': case '7': case '8': case '9': | |
+ continue; | |
+ default: | |
+ phase3_ungetc (c); | |
+ break; | |
+ } | |
+ break; | |
+ } | |
+ if (bufpos >= bufmax) | |
+ { | |
+ bufmax = 2 * bufmax + 10; | |
+ buffer = xrealloc (buffer, bufmax); | |
+ } | |
+ buffer[bufpos] = '\0'; | |
+ tp->string = xstrdup (buffer); | |
+ tp->type = last_token_type = token_type_symbol; | |
+ return; | |
+ } | |
+ | |
+ /* Strings. */ | |
+ { | |
+ struct mixed_string_buffer literal; | |
+ int quote_char; | |
+ bool interpret_ansic; | |
+ bool interpret_unicode; | |
+ unsigned int backslash_counter; | |
+ | |
+ case '"': case '\'': | |
+ quote_char = c; | |
+ interpret_ansic = true; | |
+ interpret_unicode = false; | |
+ string: | |
+ lexical_context = lc_string; | |
+ backslash_counter = 0; | |
+ /* Start accumulating the string. */ | |
+ init_mixed_string_buffer (&literal, lc_string); | |
+ for (;;) | |
+ { | |
+ int uc = phase7_getuc (quote_char, interpret_ansic, | |
+ interpret_unicode, &backslash_counter); | |
+ | |
+ if (uc == P7_EOF || uc == P7_STRING_END) | |
+ break; | |
+ | |
+ if (IS_UNICODE (uc)) | |
+ assert (UNICODE_VALUE (uc) >= 0 | |
+ && UNICODE_VALUE (uc) < 0x110000); | |
+ | |
+ mixed_string_buffer_append (&literal, uc); | |
+ } | |
+ tp->string = xstrdup (mixed_string_buffer_result (&literal)); | |
+ free_mixed_string_buffer (&literal); | |
+ tp->comment = add_reference (savable_comment); | |
+ lexical_context = lc_outside; | |
+ tp->type = last_token_type = token_type_string; | |
+ return; | |
+ } | |
+ | |
+ /* Identify operators. The multiple character ones are simply ignored | |
+ * as they are recognized here and are otherwise not relevant. */ | |
+ /* FALLTHROUGH */ | |
+ case '+': case '-': case '*': /* '/' is not listed here! */ | |
+ case '%': case '<': case '>': case '=': | |
+ case '~': case '!': case '|': case '&': case '^': | |
+ case '?': case ':': | |
+ tp->operator = c; | |
+ tp->type = last_token_type = token_type_operator; | |
+ return; | |
+ | |
+ case '/': | |
+ /* Either a division operator or the start of a RegExp literal. | |
+ * If the '/' token is spotted after a symbol it's a division, | |
+ * otherwise it's a regex */ | |
+ if (last_token_type == token_type_symbol || | |
+ last_token_type == token_type_rparen || | |
+ last_token_type == token_type_rbracket) | |
+ { | |
+ tp->operator = '/'; | |
+ tp->type = last_token_type = token_type_operator; | |
+ return; | |
+ } | |
+ else | |
+ { | |
+ phase5_scan_regexp (); | |
+ tp->type = last_token_type = token_type_other; | |
+ return; | |
+ } | |
+ | |
+ case '(': | |
+ open_pbb++; | |
+ tp->type = last_token_type = token_type_lparen; | |
+ return; | |
+ | |
+ case ')': | |
+ if (open_pbb > 0) | |
+ open_pbb--; | |
+ tp->type = last_token_type = token_type_rparen; | |
+ return; | |
+ | |
+ case ',': | |
+ tp->type = last_token_type = token_type_comma; | |
+ return; | |
+ | |
+ case '[': case '{': | |
+ open_pbb++; | |
+ tp->type = last_token_type = (c == '[' ? token_type_lbracket : token_type_other); | |
+ return; | |
+ | |
+ case ']': case '}': | |
+ if (open_pbb > 0) | |
+ open_pbb--; | |
+ tp->type = last_token_type = (c == ']' ? token_type_rbracket : token_type_other); | |
+ return; | |
+ | |
+ default: | |
+ /* We could carefully recognize each of the 2 and 3 character | |
+ operators, but it is not necessary, as we only need to recognize | |
+ gettext invocations. Don't bother. */ | |
+ tp->type = last_token_type = token_type_other; | |
+ return; | |
+ } | |
+ } | |
+} | |
+ | |
+/* Supports only one pushback token. */ | |
+static void | |
+phase5_unget (token_ty *tp) | |
+{ | |
+ if (tp->type != token_type_eof) | |
+ { | |
+ if (phase5_pushback_length == SIZEOF (phase5_pushback)) | |
+ abort (); | |
+ phase5_pushback[phase5_pushback_length++] = *tp; | |
+ } | |
+} | |
+ | |
+ | |
+/* Combine adjacent strings to form a single string. Note that the end | |
+ of a logical line appears as a token of its own, therefore strings that | |
+ belong to different logical lines will not be concatenated. */ | |
+ | |
+static void | |
+x_javascript_lex (token_ty *tp) | |
+{ | |
+ phase5_get (tp); | |
+ if (tp->type != token_type_string) | |
+ return; | |
+ for (;;) | |
+ { | |
+ token_ty tmp; | |
+ size_t len; | |
+ | |
+ phase5_get (&tmp); | |
+ if (tmp.type == token_type_operator && tmp.operator == '+') | |
+ { | |
+ /* allow concatenation of strings using the plus '+' operator */ | |
+ token_ty tmp2; | |
+ phase5_get (&tmp2); | |
+ if (tmp2.type != token_type_string) | |
+ { | |
+ phase5_unget (&tmp2); | |
+ phase5_unget (&tmp); | |
+ return; | |
+ } | |
+ memcpy(&tmp, &tmp2, sizeof(tmp)); | |
+ } | |
+ else if (tmp.type != token_type_string) | |
+ { | |
+ phase5_unget (&tmp); | |
+ return; | |
+ } | |
+ len = strlen (tp->string); | |
+ tp->string = xrealloc (tp->string, len + strlen (tmp.string) + 1); | |
+ strcpy (tp->string + len, tmp.string); | |
+ free (tmp.string); | |
+ } | |
+} | |
+ | |
+ | |
+/* ========================= Extracting strings. ========================== */ | |
+ | |
+ | |
+/* Context lookup table. */ | |
+static flag_context_list_table_ty *flag_context_list_table; | |
+ | |
+ | |
+/* The file is broken into tokens. Scan the token stream, looking for | |
+ a keyword, followed by a left paren, followed by a string. When we | |
+ see this sequence, we have something to remember. We assume we are | |
+ looking at a valid Javascript program, and leave the complaints about | |
+ the grammar to the compiler. | |
+ | |
+ Normal handling: Look for | |
+ keyword ( ... msgid ... ) | |
+ Plural handling: Look for | |
+ keyword ( ... msgid ... msgid_plural ... ) | |
+ | |
+ We use recursion because the arguments before msgid or between msgid | |
+ and msgid_plural can contain subexpressions of the same form. */ | |
+ | |
+ | |
+/* Extract messages until the next balanced closing parenthesis or bracket. | |
+ Extracted messages are added to MLP. | |
+ DELIM can be either token_type_rparen or token_type_rbracket, or | |
+ token_type_eof to accept both. | |
+ Return true upon eof, false upon closing parenthesis or bracket. */ | |
+static bool | |
+extract_balanced (message_list_ty *mlp, | |
+ token_type_ty delim, | |
+ flag_context_ty outer_context, | |
+ flag_context_list_iterator_ty context_iter, | |
+ struct arglist_parser *argparser) | |
+{ | |
+ /* Current argument number. */ | |
+ int arg = 1; | |
+ /* 0 when no keyword has been seen. 1 right after a keyword is seen. */ | |
+ int state; | |
+ /* Parameters of the keyword just seen. Defined only in state 1. */ | |
+ const struct callshapes *next_shapes = NULL; | |
+ /* Context iterator that will be used if the next token is a '('. */ | |
+ flag_context_list_iterator_ty next_context_iter = | |
+ passthrough_context_list_iterator; | |
+ /* Current context. */ | |
+ flag_context_ty inner_context = | |
+ inherited_context (outer_context, | |
+ flag_context_list_iterator_advance (&context_iter)); | |
+ | |
+ /* Start state is 0. */ | |
+ state = 0; | |
+ | |
+ for (;;) | |
+ { | |
+ token_ty token; | |
+ | |
+ x_javascript_lex (&token); | |
+ switch (token.type) | |
+ { | |
+ case token_type_symbol: | |
+ { | |
+ void *keyword_value; | |
+ | |
+ if (hash_find_entry (&keywords, token.string, strlen (token.string), | |
+ &keyword_value) | |
+ == 0) | |
+ { | |
+ next_shapes = (const struct callshapes *) keyword_value; | |
+ state = 1; | |
+ } | |
+ else | |
+ state = 0; | |
+ } | |
+ next_context_iter = | |
+ flag_context_list_iterator ( | |
+ flag_context_list_table_lookup ( | |
+ flag_context_list_table, | |
+ token.string, strlen (token.string))); | |
+ free (token.string); | |
+ continue; | |
+ | |
+ case token_type_lparen: | |
+ if (extract_balanced (mlp, token_type_rparen, | |
+ inner_context, next_context_iter, | |
+ arglist_parser_alloc (mlp, | |
+ state ? next_shapes : NULL))) | |
+ { | |
+ xgettext_current_source_encoding = po_charset_utf8; | |
+ arglist_parser_done (argparser, arg); | |
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding; | |
+ return true; | |
+ } | |
+ next_context_iter = null_context_list_iterator; | |
+ state = 0; | |
+ continue; | |
+ | |
+ case token_type_rparen: | |
+ if (delim == token_type_rparen || delim == token_type_eof) | |
+ { | |
+ xgettext_current_source_encoding = po_charset_utf8; | |
+ arglist_parser_done (argparser, arg); | |
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding; | |
+ return false; | |
+ } | |
+ next_context_iter = null_context_list_iterator; | |
+ state = 0; | |
+ continue; | |
+ | |
+ case token_type_comma: | |
+ arg++; | |
+ inner_context = | |
+ inherited_context (outer_context, | |
+ flag_context_list_iterator_advance ( | |
+ &context_iter)); | |
+ next_context_iter = passthrough_context_list_iterator; | |
+ state = 0; | |
+ continue; | |
+ | |
+ case token_type_lbracket: | |
+ if (extract_balanced (mlp, token_type_rbracket, | |
+ null_context, null_context_list_iterator, | |
+ arglist_parser_alloc (mlp, NULL))) | |
+ { | |
+ xgettext_current_source_encoding = po_charset_utf8; | |
+ arglist_parser_done (argparser, arg); | |
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding; | |
+ return true; | |
+ } | |
+ next_context_iter = null_context_list_iterator; | |
+ state = 0; | |
+ continue; | |
+ | |
+ case token_type_rbracket: | |
+ if (delim == token_type_rbracket || delim == token_type_eof) | |
+ { | |
+ xgettext_current_source_encoding = po_charset_utf8; | |
+ arglist_parser_done (argparser, arg); | |
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding; | |
+ return false; | |
+ } | |
+ next_context_iter = null_context_list_iterator; | |
+ state = 0; | |
+ continue; | |
+ | |
+ case token_type_string: | |
+ { | |
+ lex_pos_ty pos; | |
+ pos.file_name = logical_file_name; | |
+ pos.line_number = token.line_number; | |
+ | |
+ xgettext_current_source_encoding = po_charset_utf8; | |
+ if (extract_all) | |
+ remember_a_message (mlp, NULL, token.string, inner_context, | |
+ &pos, NULL, token.comment); | |
+ else | |
+ arglist_parser_remember (argparser, arg, token.string, | |
+ inner_context, | |
+ pos.file_name, pos.line_number, | |
+ token.comment); | |
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding; | |
+ } | |
+ drop_reference (token.comment); | |
+ next_context_iter = null_context_list_iterator; | |
+ state = 0; | |
+ continue; | |
+ | |
+ case token_type_eof: | |
+ xgettext_current_source_encoding = po_charset_utf8; | |
+ arglist_parser_done (argparser, arg); | |
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding; | |
+ return true; | |
+ | |
+ /* FALLTHROUGH */ | |
+ case token_type_regexp: | |
+ case token_type_operator: | |
+ case token_type_other: | |
+ next_context_iter = null_context_list_iterator; | |
+ state = 0; | |
+ continue; | |
+ | |
+ default: | |
+ abort (); | |
+ } | |
+ } | |
+} | |
+ | |
+ | |
+void | |
+extract_javascript (FILE *f, | |
+ const char *real_filename, const char *logical_filename, | |
+ flag_context_list_table_ty *flag_table, | |
+ msgdomain_list_ty *mdlp) | |
+{ | |
+ message_list_ty *mlp = mdlp->item[0]->messages; | |
+ | |
+ fp = f; | |
+ real_file_name = real_filename; | |
+ logical_file_name = xstrdup (logical_filename); | |
+ line_number = 1; | |
+ | |
+ lexical_context = lc_outside; | |
+ | |
+ last_comment_line = -1; | |
+ last_non_comment_line = -1; | |
+ | |
+ xgettext_current_file_source_encoding = xgettext_global_source_encoding; | |
+#if HAVE_ICONV | |
+ xgettext_current_file_source_iconv = xgettext_global_source_iconv; | |
+#endif | |
+ | |
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding; | |
+#if HAVE_ICONV | |
+ xgettext_current_source_iconv = xgettext_current_file_source_iconv; | |
+#endif | |
+ | |
+ continuation_or_nonblank_line = false; | |
+ | |
+ open_pbb = 0; | |
+ | |
+ flag_context_list_table = flag_table; | |
+ | |
+ init_keywords (); | |
+ | |
+ /* Eat tokens until eof is seen. When extract_balanced returns | |
+ due to an unbalanced closing parenthesis, just restart it. */ | |
+ while (!extract_balanced (mlp, token_type_eof, | |
+ null_context, null_context_list_iterator, | |
+ arglist_parser_alloc (mlp, NULL))) | |
+ ; | |
+ | |
+ fp = NULL; | |
+ real_file_name = NULL; | |
+ logical_file_name = NULL; | |
+ line_number = 0; | |
+} | |
diff --git c/gettext-tools/src/x-javascript.h i/gettext-tools/src/x-javascript.h | |
new file mode 100644 | |
index 0000000..d668136 | |
--- /dev/null | |
+++ i/gettext-tools/src/x-javascript.h | |
@@ -0,0 +1,52 @@ | |
+/* xgettext Python backend. | |
+ Copyright (C) 2002-2003, 2006 Free Software Foundation, Inc. | |
+ This file was written by Andreas Stricker <[email protected]>, 2010. | |
+ It's based on x-python from Bruno Haible. | |
+ | |
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>. */ | |
+ | |
+ | |
+#include <stdio.h> | |
+ | |
+#include "message.h" | |
+#include "xgettext.h" | |
+ | |
+ | |
+#ifdef __cplusplus | |
+extern "C" { | |
+#endif | |
+ | |
+ | |
+#define EXTENSIONS_JAVASCRIPT \ | |
+ { "js", "JavaScript" }, \ | |
+ | |
+#define SCANNERS_JAVASCRIPT \ | |
+ { "JavaScript", extract_javascript, \ | |
+ &flag_table_javascript, &formatstring_javascript, NULL }, \ | |
+ | |
+/* Scan a Python file and add its translatable strings to mdlp. */ | |
+extern void extract_javascript (FILE *fp, const char *real_filename, | |
+ const char *logical_filename, | |
+ flag_context_list_table_ty *flag_table, | |
+ msgdomain_list_ty *mdlp); | |
+ | |
+extern void x_javascript_keyword (const char *keyword); | |
+extern void x_javascript_extract_all (void); | |
+ | |
+extern void init_flag_table_javascript (void); | |
+ | |
+ | |
+#ifdef __cplusplus | |
+} | |
+#endif | |
diff --git c/gettext-tools/src/xgettext.c i/gettext-tools/src/xgettext.c | |
index eb3271f..c4b39db 100644 | |
--- c/gettext-tools/src/xgettext.c | |
+++ i/gettext-tools/src/xgettext.c | |
@@ -82,6 +82,7 @@ | |
#include "x-scheme.h" | |
#include "x-smalltalk.h" | |
#include "x-java.h" | |
+#include "x-javascript.h" | |
#include "x-properties.h" | |
#include "x-csharp.h" | |
#include "x-awk.h" | |
@@ -154,6 +155,7 @@ static flag_context_list_table_ty flag_table_elisp; | |
static flag_context_list_table_ty flag_table_librep; | |
static flag_context_list_table_ty flag_table_scheme; | |
static flag_context_list_table_ty flag_table_java; | |
+static flag_context_list_table_ty flag_table_javascript; | |
static flag_context_list_table_ty flag_table_csharp; | |
static flag_context_list_table_ty flag_table_awk; | |
static flag_context_list_table_ty flag_table_ycp; | |
@@ -325,6 +327,7 @@ main (int argc, char *argv[]) | |
init_flag_table_librep (); | |
init_flag_table_scheme (); | |
init_flag_table_java (); | |
+ init_flag_table_javascript (); | |
init_flag_table_csharp (); | |
init_flag_table_awk (); | |
init_flag_table_ycp (); | |
@@ -349,6 +352,7 @@ main (int argc, char *argv[]) | |
x_librep_extract_all (); | |
x_scheme_extract_all (); | |
x_java_extract_all (); | |
+ x_javascript_extract_all (); | |
x_csharp_extract_all (); | |
x_awk_extract_all (); | |
x_tcl_extract_all (); | |
@@ -426,6 +430,7 @@ main (int argc, char *argv[]) | |
x_librep_keyword (optarg); | |
x_scheme_keyword (optarg); | |
x_java_keyword (optarg); | |
+ x_javascript_keyword (optarg); | |
x_csharp_keyword (optarg); | |
x_awk_keyword (optarg); | |
x_tcl_keyword (optarg); | |
@@ -856,8 +861,9 @@ Choice of input file language:\n")); | |
-L, --language=NAME recognise the specified language\n\ | |
(C, C++, ObjectiveC, PO, Shell, Python, Lisp,\n\ | |
EmacsLisp, librep, Scheme, Smalltalk, Java,\n\ | |
- JavaProperties, C#, awk, YCP, Tcl, Perl, PHP,\n\ | |
- GCC-source, NXStringTable, RST, Glade)\n")); | |
+ JavaProperties, JavaScript, C#, awk, YCP, Tcl,\n\ | |
+ Perl, PHP, GCC-source, NXStringTable, RST,\n\ | |
+ Glade)\n")); | |
printf (_("\ | |
-C, --c++ shorthand for --language=C++\n")); | |
printf (_("\ | |
@@ -1701,6 +1707,11 @@ xgettext_record_flag (const char *optionstring) | |
name_start, name_end, | |
argnum, value, pass); | |
break; | |
+ case format_javascript: | |
+ flag_context_list_table_insert (&flag_table_javascript, 0, | |
+ name_start, name_end, | |
+ argnum, value, pass); | |
+ break; | |
case format_csharp: | |
flag_context_list_table_insert (&flag_table_csharp, 0, | |
name_start, name_end, | |
@@ -3172,6 +3183,7 @@ language_to_extractor (const char *name) | |
SCANNERS_SCHEME | |
SCANNERS_SMALLTALK | |
SCANNERS_JAVA | |
+ SCANNERS_JAVASCRIPT | |
SCANNERS_PROPERTIES | |
SCANNERS_CSHARP | |
SCANNERS_AWK | |
@@ -3255,6 +3267,7 @@ extension_to_language (const char *extension) | |
EXTENSIONS_SCHEME | |
EXTENSIONS_SMALLTALK | |
EXTENSIONS_JAVA | |
+ EXTENSIONS_JAVASCRIPT | |
EXTENSIONS_PROPERTIES | |
EXTENSIONS_CSHARP | |
EXTENSIONS_AWK | |
diff --git c/gettext-tools/tests/Makefile.am i/gettext-tools/tests/Makefile.am | |
index f875913..5797104 100644 | |
--- c/gettext-tools/tests/Makefile.am | |
+++ i/gettext-tools/tests/Makefile.am | |
@@ -82,6 +82,7 @@ TESTS = gettext-1 gettext-2 gettext-3 gettext-4 gettext-5 gettext-6 gettext-7 \ | |
xgettext-glade-1 xgettext-glade-2 xgettext-glade-3 xgettext-glade-4 \ | |
xgettext-java-1 xgettext-java-2 xgettext-java-3 xgettext-java-4 \ | |
xgettext-java-5 xgettext-java-6 xgettext-java-7 \ | |
+ xgettext-javascript-1 xgettext-javascript-2 xgettext-javascript-3 \ | |
xgettext-librep-1 xgettext-librep-2 \ | |
xgettext-lisp-1 xgettext-lisp-2 \ | |
xgettext-objc-1 xgettext-objc-2 \ | |
diff --git c/gettext-tools/tests/Makefile.in i/gettext-tools/tests/Makefile.in | |
index bb59f1f..124a125 100644 | |
--- c/gettext-tools/tests/Makefile.in | |
+++ i/gettext-tools/tests/Makefile.in | |
@@ -1595,6 +1595,7 @@ TESTS = gettext-1 gettext-2 gettext-3 gettext-4 gettext-5 gettext-6 gettext-7 \ | |
xgettext-glade-1 xgettext-glade-2 xgettext-glade-3 xgettext-glade-4 \ | |
xgettext-java-1 xgettext-java-2 xgettext-java-3 xgettext-java-4 \ | |
xgettext-java-5 xgettext-java-6 xgettext-java-7 \ | |
+ xgettext-javascript-1 xgettext-javascript-2 xgettext-javascript-3 \ | |
xgettext-librep-1 xgettext-librep-2 \ | |
xgettext-lisp-1 xgettext-lisp-2 \ | |
xgettext-objc-1 xgettext-objc-2 \ | |
diff --git c/gettext-tools/tests/xgettext-javascript-1 i/gettext-tools/tests/xgettext-javascript-1 | |
new file mode 100644 | |
index 0000000..e99523d | |
--- /dev/null | |
+++ i/gettext-tools/tests/xgettext-javascript-1 | |
@@ -0,0 +1,64 @@ | |
+#!/bin/sh | |
+ | |
+# Test of Javascript support. | |
+ | |
+tmpfiles="" | |
+trap 'rm -fr $tmpfiles' 1 2 3 15 | |
+ | |
+tmpfiles="$tmpfiles xg-js-1.js" | |
+cat <<\EOF > xg-js-1.js | |
+var s1 = "Simple string, no gettext needed", | |
+ s2 = _("Extract this first string"); | |
+function foo(a) { | |
+ var s3 = "Prefix _(" + _("Extract this second string") + ") Postfix"; | |
+} | |
+if (document.getElementsById("foo")[0].innerHTML == _("Extract this thirth string")) { | |
+ /* _("This is a comment and must not be extracted!") */ | |
+} | |
+EOF | |
+ | |
+tmpfiles="$tmpfiles xg-js-1.err xg-js-1.tmp xg-js-1.pot" | |
+: ${XGETTEXT=xgettext} | |
+${XGETTEXT} --add-comments --no-location -o xg-js-1.tmp xg-js-1.js 2>xg-js-1.err | |
+test $? = 0 || { cat xg-js-1.err; rm -fr $tmpfiles; exit 1; } | |
+# Don't simplify this to "grep ... < xg-js-1.tmp", otherwise OpenBSD 4.0 grep | |
+# only outputs "Binary file (standard input) matches". | |
+cat xg-js-1.tmp | grep -v 'POT-Creation-Date' | LC_ALL=C tr -d '\r' > xg-js-1.pot | |
+ | |
+tmpfiles="$tmpfiles xg-js-1.ok" | |
+cat <<\EOF > xg-js-1.ok | |
+# SOME DESCRIPTIVE TITLE. | |
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER | |
+# This file is distributed under the same license as the PACKAGE package. | |
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. | |
+# | |
+#, fuzzy | |
+msgid "" | |
+msgstr "" | |
+"Project-Id-Version: PACKAGE VERSION\n" | |
+"Report-Msgid-Bugs-To: \n" | |
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | |
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | |
+"Language-Team: LANGUAGE <[email protected]>\n" | |
+"Language: \n" | |
+"MIME-Version: 1.0\n" | |
+"Content-Type: text/plain; charset=CHARSET\n" | |
+"Content-Transfer-Encoding: 8bit\n" | |
+ | |
+msgid "Extract this first string" | |
+msgstr "" | |
+ | |
+msgid "Extract this second string" | |
+msgstr "" | |
+ | |
+msgid "Extract this thirth string" | |
+msgstr "" | |
+EOF | |
+ | |
+: ${DIFF=diff} | |
+${DIFF} xg-js-1.ok xg-js-1.pot | |
+result=$? | |
+ | |
+rm -fr $tmpfiles | |
+ | |
+exit $result | |
diff --git c/gettext-tools/tests/xgettext-javascript-2 i/gettext-tools/tests/xgettext-javascript-2 | |
new file mode 100644 | |
index 0000000..9766646 | |
--- /dev/null | |
+++ i/gettext-tools/tests/xgettext-javascript-2 | |
@@ -0,0 +1,91 @@ | |
+#!/bin/sh | |
+ | |
+# Test of Javascript support. | |
+# Playing with regex and division operator | |
+ | |
+tmpfiles="" | |
+trap 'rm -fr $tmpfiles' 1 2 3 15 | |
+ | |
+tmpfiles="$tmpfiles xg-js-2.js" | |
+cat <<\EOF > xg-js-2.js | |
+// RegExp literals containing string quotes must not desync the parser | |
+var d = 1 / 2 / 4; | |
+var s = " x " + /^\d/.match("0815").replace(/[a-z]/g, '@'); | |
+var s1 = /"/.match(_("RegExp test string #1")); | |
+var s2 = /'/.match(_("RegExp test string #2")); | |
+var s3 = /['a-b]/.match(_('RegExp test string #3')); | |
+var s4 = /["a-b]/.match(_('RegExp test string #4')); | |
+var s5 = /[a-b']/.match(_('RegExp test string #5')); | |
+var s6 = /[a-b"]/.match(_('RegExp test string #6')); | |
+var c = 35 / 2 / 8 + _("RegExp test string #7").length / 32.0; | |
+var sizestr = Math.round(size/1024*factor)/factor+_("RegExp test string #8"); | |
+var cssClassType = attr.type.replace(/^.*\//, _('RegExp test string #9')).replace(/\./g, '-'); | |
+var lookup = lookuptable[idx]/factor+_("RegExp test string #10"); | |
+EOF | |
+ | |
+tmpfiles="$tmpfiles xg-js-2.err xg-js-2.tmp xg-js-2.pot" | |
+: ${XGETTEXT=xgettext} | |
+${XGETTEXT} --add-comments --no-location -o xg-js-2.tmp xg-js-2.js 2>xg-js-2.err | |
+test $? = 0 || { cat xg-js-2.err; rm -fr $tmpfiles; exit 1; } | |
+# Don't simplify this to "grep ... < xg-js-2.tmp", otherwise OpenBSD 4.0 grep | |
+# only outputs "Binary file (standard input) matches". | |
+cat xg-js-2.tmp | grep -v 'POT-Creation-Date' | LC_ALL=C tr -d '\r' > xg-js-2.pot | |
+ | |
+tmpfiles="$tmpfiles xg-js-2.ok" | |
+cat <<\EOF > xg-js-2.ok | |
+# SOME DESCRIPTIVE TITLE. | |
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER | |
+# This file is distributed under the same license as the PACKAGE package. | |
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. | |
+# | |
+#, fuzzy | |
+msgid "" | |
+msgstr "" | |
+"Project-Id-Version: PACKAGE VERSION\n" | |
+"Report-Msgid-Bugs-To: \n" | |
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | |
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | |
+"Language-Team: LANGUAGE <[email protected]>\n" | |
+"Language: \n" | |
+"MIME-Version: 1.0\n" | |
+"Content-Type: text/plain; charset=CHARSET\n" | |
+"Content-Transfer-Encoding: 8bit\n" | |
+ | |
+msgid "RegExp test string #1" | |
+msgstr "" | |
+ | |
+msgid "RegExp test string #2" | |
+msgstr "" | |
+ | |
+msgid "RegExp test string #3" | |
+msgstr "" | |
+ | |
+msgid "RegExp test string #4" | |
+msgstr "" | |
+ | |
+msgid "RegExp test string #5" | |
+msgstr "" | |
+ | |
+msgid "RegExp test string #6" | |
+msgstr "" | |
+ | |
+msgid "RegExp test string #7" | |
+msgstr "" | |
+ | |
+msgid "RegExp test string #8" | |
+msgstr "" | |
+ | |
+msgid "RegExp test string #9" | |
+msgstr "" | |
+ | |
+msgid "RegExp test string #10" | |
+msgstr "" | |
+EOF | |
+ | |
+: ${DIFF=diff} | |
+${DIFF} xg-js-2.ok xg-js-2.pot | |
+result=$? | |
+ | |
+rm -fr $tmpfiles | |
+ | |
+exit $result | |
diff --git c/gettext-tools/tests/xgettext-javascript-3 i/gettext-tools/tests/xgettext-javascript-3 | |
new file mode 100644 | |
index 0000000..44e5366 | |
--- /dev/null | |
+++ i/gettext-tools/tests/xgettext-javascript-3 | |
@@ -0,0 +1,73 @@ | |
+#!/bin/sh | |
+ | |
+# Test of Javascript support. | |
+# Playing with concatenation of string literals withing the gettext function | |
+ | |
+tmpfiles="" | |
+trap 'rm -fr $tmpfiles' 1 2 3 15 | |
+ | |
+tmpfiles="$tmpfiles xg-js-2.js" | |
+cat <<\EOF > xg-js-2.js | |
+// The usual way to concatenate strings is the plus '+' sign | |
+var s1 = _("Concatenation #1 " "- String part added"); | |
+var s2 = _('Concatenation #2 ' + '- String part added'); | |
+var s3 = _('Concatenation #3 ' | |
+ '- String part added'); | |
+var s4 = _('Concatenation #4 ' + | |
+ '- String part added'); | |
+var s5 = _("This" + " whole " | |
+ + "string" + | |
+ ' should' + " be " + 'extracted'); | |
+EOF | |
+ | |
+tmpfiles="$tmpfiles xg-js-2.err xg-js-2.tmp xg-js-2.pot" | |
+: ${XGETTEXT=xgettext} | |
+${XGETTEXT} --add-comments --no-location -o xg-js-2.tmp xg-js-2.js 2>xg-js-2.err | |
+test $? = 0 || { cat xg-js-2.err; rm -fr $tmpfiles; exit 1; } | |
+# Don't simplify this to "grep ... < xg-js-2.tmp", otherwise OpenBSD 4.0 grep | |
+# only outputs "Binary file (standard input) matches". | |
+cat xg-js-2.tmp | grep -v 'POT-Creation-Date' | LC_ALL=C tr -d '\r' > xg-js-2.pot | |
+ | |
+tmpfiles="$tmpfiles xg-js-2.ok" | |
+cat <<\EOF > xg-js-2.ok | |
+# SOME DESCRIPTIVE TITLE. | |
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER | |
+# This file is distributed under the same license as the PACKAGE package. | |
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. | |
+# | |
+#, fuzzy | |
+msgid "" | |
+msgstr "" | |
+"Project-Id-Version: PACKAGE VERSION\n" | |
+"Report-Msgid-Bugs-To: \n" | |
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | |
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | |
+"Language-Team: LANGUAGE <[email protected]>\n" | |
+"Language: \n" | |
+"MIME-Version: 1.0\n" | |
+"Content-Type: text/plain; charset=CHARSET\n" | |
+"Content-Transfer-Encoding: 8bit\n" | |
+ | |
+msgid "Concatenation #1 - String part added" | |
+msgstr "" | |
+ | |
+msgid "Concatenation #2 - String part added" | |
+msgstr "" | |
+ | |
+msgid "Concatenation #3 - String part added" | |
+msgstr "" | |
+ | |
+msgid "Concatenation #4 - String part added" | |
+msgstr "" | |
+ | |
+msgid "This whole string should be extracted" | |
+msgstr "" | |
+EOF | |
+ | |
+: ${DIFF=diff} | |
+${DIFF} xg-js-2.ok xg-js-2.pot | |
+result=$? | |
+ | |
+rm -fr $tmpfiles | |
+ | |
+exit $result | |
diff --git c/gettext-tools/woe32dll/gettextsrc-exports.c i/gettext-tools/woe32dll/gettextsrc-exports.c | |
index 44ff5c5..7c9c59b 100644 | |
--- c/gettext-tools/woe32dll/gettextsrc-exports.c | |
+++ i/gettext-tools/woe32dll/gettextsrc-exports.c | |
@@ -30,6 +30,7 @@ VARIABLE(formatstring_elisp) | |
VARIABLE(formatstring_gcc_internal) | |
VARIABLE(formatstring_gfc_internal) | |
VARIABLE(formatstring_java) | |
+VARIABLE(formatstring_javascript) | |
VARIABLE(formatstring_kde) | |
VARIABLE(formatstring_librep) | |
VARIABLE(formatstring_lisp) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment