]> git.ipfire.org Git - thirdparty/gettext.git/commitdiff
Add Go support.
authorBruno Haible <bruno@clisp.org>
Fri, 7 Mar 2025 11:24:50 +0000 (12:24 +0100)
committerBruno Haible <bruno@clisp.org>
Fri, 7 Mar 2025 11:42:54 +0000 (12:42 +0100)
* autopull.sh: Check out tree-sitter-go. Set TREE_SITTER_GO_VERSION.
* autogen.sh (GNULIB_MODULES_TOOLS_FOR_SRC): Add carray-list, hash-map,
hash-set, xlist, xmap, xset, xstring-buffer-reversed.
* gettext-tools/build-aux/tree-sitter-go-portability.diff: New file.
* gettext-tools/configure.ac: Set TREE_SITTER_GO_VERSION.
* gettext-tools/Makefile.am (EXTRA_DIST): Add the tree-sitter-go source code and
patch.
* gettext-tools/doc/lang-go.texi: New file.
* gettext-tools/doc/Makefile.am (gettext_TEXINFOS): Add it.
* gettext-tools/doc/gettext.texi (PO Files): Mention go-format.
(No string concatenation): Mention string concatenation in Go.
(Translators for other Languages): New subsection "Go Format Strings".
(List of Programming Languages): Include lang-go.texi.
* gettext-tools/doc/xgettext.texi: Document the -L Go option.
* gettext-tools/src/message.h (format_go): New enum value.
(NFORMATS): Increment.
* gettext-tools/src/message.c (format_language, format_language_pretty): Add an
entry for format_go.
* gettext-tools/src/format-go.c: New file.
* gettext-tools/src/format.h (formatstring_go): New declaration.
* gettext-tools/src/format.c (formatstring_parsers): Add formatstring_go.
* gettext-tools/src/x-go.h: New file.
* gettext-tools/src/x-go.c: New file.
* gettext-tools/src/xgettext.c: Include x-go.h.
(flag_table_go): New variable.
(main): Invoke init_flag_table_go, x_go_extract_all, x_go_keyword.
(usage): Document the -L Go option.
(xgettext_record_flag): Support format_go.
(language_to_extractor, extension_to_language): Support Go.
* gettext-tools/src/FILES: Mention format-go.c, x-go.h, x-go.c.
* gettext-tools/src/Makefile.am (noinst_HEADERS): Add x-go.h.
(FORMAT_SOURCE): Add format-go.c.
(libxgettextts2_a_SOURCES): Add go-parser.c.
(xgettext_SOURCES): Add x-go.c.
* gettext-tools/libgettextpo/Makefile.am (libgettextpo_la_AUXSOURCES): Add
format-go.c.
* gettext-tools/woe32dll/gettextsrc-exports.c: Export formatstring_go.
* gettext-tools/tests/format-go-1: New file.
* gettext-tools/tests/format-go-2: New file.
* gettext-tools/tests/xgettext-go-1: New file.
* gettext-tools/tests/xgettext-go-2: New file.
* gettext-tools/tests/xgettext-go-3: New file.
* gettext-tools/tests/xgettext-go-4: New file.
* gettext-tools/tests/xgettext-go-5: New file.
* gettext-tools/tests/xgettext-go-6: New file.
* gettext-tools/tests/xgettext-go-7: New file.
* gettext-tools/tests/xgettext-go-8: New file.
* gettext-tools/tests/xgettext-go-9: New file.
* gettext-tools/tests/xgettext-go-10: New file.
* gettext-tools/tests/xgettext-go-11: New file.
* gettext-tools/tests/xgettext-go-12: New file.
* gettext-tools/tests/xgettext-go-13: New file.
* gettext-tools/tests/xgettext-go-14: New file.
* gettext-tools/tests/xgettext-go-15: New file.
* gettext-tools/tests/xgettext-go-16: New file.
* gettext-tools/tests/xgettext-go-17: New file.
* gettext-tools/tests/xgettext-go-18: New file.
* gettext-tools/tests/xgettext-go-stackovfl-1: New file.
* gettext-tools/tests/xgettext-go-stackovfl-2: New file.
* gettext-tools/tests/Makefile.am (TESTS): Add the new tests.
* NEWS: Mention the Go support.

45 files changed:
NEWS
autogen.sh
autopull.sh
gettext-tools/Makefile.am
gettext-tools/build-aux/tree-sitter-go-portability.diff [new file with mode: 0644]
gettext-tools/configure.ac
gettext-tools/doc/Makefile.am
gettext-tools/doc/gettext.texi
gettext-tools/doc/lang-go.texi [new file with mode: 0644]
gettext-tools/doc/xgettext.texi
gettext-tools/libgettextpo/Makefile.am
gettext-tools/src/FILES
gettext-tools/src/Makefile.am
gettext-tools/src/format-go.c [new file with mode: 0644]
gettext-tools/src/format.c
gettext-tools/src/format.h
gettext-tools/src/message.c
gettext-tools/src/message.h
gettext-tools/src/x-go.c [new file with mode: 0644]
gettext-tools/src/x-go.h [new file with mode: 0644]
gettext-tools/src/xgettext.c
gettext-tools/tests/Makefile.am
gettext-tools/tests/format-go-1 [new file with mode: 0755]
gettext-tools/tests/format-go-2 [new file with mode: 0755]
gettext-tools/tests/xgettext-go-1 [new file with mode: 0755]
gettext-tools/tests/xgettext-go-10 [new file with mode: 0755]
gettext-tools/tests/xgettext-go-11 [new file with mode: 0755]
gettext-tools/tests/xgettext-go-12 [new file with mode: 0755]
gettext-tools/tests/xgettext-go-13 [new file with mode: 0755]
gettext-tools/tests/xgettext-go-14 [new file with mode: 0755]
gettext-tools/tests/xgettext-go-15 [new file with mode: 0755]
gettext-tools/tests/xgettext-go-16 [new file with mode: 0755]
gettext-tools/tests/xgettext-go-17 [new file with mode: 0755]
gettext-tools/tests/xgettext-go-18 [new file with mode: 0755]
gettext-tools/tests/xgettext-go-2 [new file with mode: 0755]
gettext-tools/tests/xgettext-go-3 [new file with mode: 0755]
gettext-tools/tests/xgettext-go-4 [new file with mode: 0755]
gettext-tools/tests/xgettext-go-5 [new file with mode: 0755]
gettext-tools/tests/xgettext-go-6 [new file with mode: 0755]
gettext-tools/tests/xgettext-go-7 [new file with mode: 0755]
gettext-tools/tests/xgettext-go-8 [new file with mode: 0755]
gettext-tools/tests/xgettext-go-9 [new file with mode: 0755]
gettext-tools/tests/xgettext-go-stackovfl-1 [new file with mode: 0755]
gettext-tools/tests/xgettext-go-stackovfl-2 [new file with mode: 0755]
gettext-tools/woe32dll/gettextsrc-exports.c

diff --git a/NEWS b/NEWS
index 01415bc98c123e19c41529cb644113a3c3703782..9a2d88ce73c6d6d37469618f454417b85ff19307 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,11 @@
+Version 0.25 - March 2025
+
+# Programming languages support:
+  * Go:
+    - xgettext now supports Go.
+    - 'msgfmt -c' now verifies the syntax of translations of Go format
+      strings.
+
 Version 0.24 - February 2025
 
 # Programming languages support:
index 5c39845a4fb551e08faffd97c57c20ade2380833..24e2c61a33a44dea86daa55f11eb35496a32bbe0 100755 (executable)
@@ -178,6 +178,7 @@ if ! $skip_gnulib; then
     c-strcase
     c-strcasestr
     c-strstr
+    carray-list
     clean-temp
     closedir
     closeout
@@ -202,6 +203,8 @@ if ! $skip_gnulib; then
     getline
     getopt-gnu
     gettext-h
+    hash-map
+    hash-set
     iconv
     javacomp
     javaexec
@@ -280,13 +283,17 @@ if ! $skip_gnulib; then
     xalloc
     xconcat-filename
     xerror
+    xlist
     xmalloca
+    xmap
     xmemdup0
+    xset
     xsetenv
     xstrerror
     xstriconv
     xstriconveh
     xstring-buffer
+    xstring-buffer-reversed
     xstring-desc
     xvasprintf
   '
index 87dd47806407fe57fd50233c2e06fce0c0560066..2b39f66c3d1893a9123e4f2401b22be97a4a07b5 100755 (executable)
@@ -86,6 +86,7 @@ func_git_clone_shallow ()
 
 # Fetch the compilable (mostly generated) tree-sitter source code.
 TREE_SITTER_VERSION=0.23.2
+TREE_SITTER_GO_VERSION=0.23.4
 TREE_SITTER_RUST_VERSION=0.23.2
 # Cache the relevant source code. Erase the rest of the tree-sitter projects.
 test -d gettext-tools/tree-sitter-$TREE_SITTER_VERSION || {
@@ -96,6 +97,15 @@ test -d gettext-tools/tree-sitter-$TREE_SITTER_VERSION || {
   mv tree-sitter/lib gettext-tools/tree-sitter-$TREE_SITTER_VERSION/lib
   rm -rf tree-sitter
 }
+test -d gettext-tools/tree-sitter-go-$TREE_SITTER_GO_VERSION || {
+  func_git_clone_shallow tree-sitter-go https://github.com/tree-sitter/tree-sitter-go.git v$TREE_SITTER_GO_VERSION
+  (cd tree-sitter-go && patch -p1) < gettext-tools/build-aux/tree-sitter-go-portability.diff
+  mkdir gettext-tools/tree-sitter-go-$TREE_SITTER_GO_VERSION
+  mv tree-sitter-go/LICENSE gettext-tools/tree-sitter-go-$TREE_SITTER_GO_VERSION/LICENSE
+  mv tree-sitter-go/src gettext-tools/tree-sitter-go-$TREE_SITTER_GO_VERSION/src
+  mv gettext-tools/tree-sitter-go-$TREE_SITTER_GO_VERSION/src/parser.c gettext-tools/tree-sitter-go-$TREE_SITTER_GO_VERSION/src/go-parser.c
+  rm -rf tree-sitter-go
+}
 test -d gettext-tools/tree-sitter-rust-$TREE_SITTER_RUST_VERSION || {
   func_git_clone_shallow tree-sitter-rust https://github.com/tree-sitter/tree-sitter-rust.git v$TREE_SITTER_RUST_VERSION
   (cd tree-sitter-rust && patch -p1) < gettext-tools/build-aux/tree-sitter-rust-portability.diff
@@ -108,6 +118,7 @@ test -d gettext-tools/tree-sitter-rust-$TREE_SITTER_RUST_VERSION || {
 }
 cat > gettext-tools/tree-sitter.cfg <<EOF
 TREE_SITTER_VERSION=$TREE_SITTER_VERSION
+TREE_SITTER_GO_VERSION=$TREE_SITTER_GO_VERSION
 TREE_SITTER_RUST_VERSION=$TREE_SITTER_RUST_VERSION
 EOF
 
index 0b07b2a5151e3f732382b2b4c874d28b80f6507d..b51cb4012b4f0c1e5dfd96e4a0ae1fa02badca20 100644 (file)
@@ -74,6 +74,12 @@ EXTRA_DIST += \
   tree-sitter-$(TREE_SITTER_VERSION)/lib/src/wasm_store.c \
   tree-sitter-$(TREE_SITTER_VERSION)/lib/src/wasm_store.h \
   tree-sitter-$(TREE_SITTER_VERSION)/lib/src/wasm/wasm-stdlib.h \
+  build-aux/tree-sitter-go-portability.diff \
+  tree-sitter-go-$(TREE_SITTER_GO_VERSION)/LICENSE \
+  tree-sitter-go-$(TREE_SITTER_GO_VERSION)/src/go-parser.c \
+  tree-sitter-go-$(TREE_SITTER_GO_VERSION)/src/tree_sitter/alloc.h \
+  tree-sitter-go-$(TREE_SITTER_GO_VERSION)/src/tree_sitter/array.h \
+  tree-sitter-go-$(TREE_SITTER_GO_VERSION)/src/tree_sitter/parser.h \
   build-aux/tree-sitter-rust-portability.diff \
   tree-sitter-rust-$(TREE_SITTER_RUST_VERSION)/LICENSE \
   tree-sitter-rust-$(TREE_SITTER_RUST_VERSION)/src/rust-parser.c \
diff --git a/gettext-tools/build-aux/tree-sitter-go-portability.diff b/gettext-tools/build-aux/tree-sitter-go-portability.diff
new file mode 100644 (file)
index 0000000..596b79b
--- /dev/null
@@ -0,0 +1,32 @@
+diff --git a/src/parser.c b/src/parser.c
+index 8933dda..cb343ab 100644
+--- a/src/parser.c
++++ b/src/parser.c
+@@ -60528,8 +60528,10 @@ extern "C" {
+ #define TS_PUBLIC
+ #elif defined(_WIN32)
+ #define TS_PUBLIC __declspec(dllexport)
+-#else
++#elif defined __GNUC__ || defined __clang__
+ #define TS_PUBLIC __attribute__((visibility("default")))
++#else
++#define TS_PUBLIC
+ #endif
+ TS_PUBLIC const TSLanguage *tree_sitter_go(void) {
+diff --git a/src/tree_sitter/parser.h b/src/tree_sitter/parser.h
+index 799f599..130b4d0 100644
+--- a/src/tree_sitter/parser.h
++++ b/src/tree_sitter/parser.h
+@@ -155,8 +155,10 @@ static inline bool set_contains(TSCharacterRange *ranges, uint32_t len, int32_t
+ #ifdef _MSC_VER
+ #define UNUSED __pragma(warning(suppress : 4101))
+-#else
++#elif defined __GNUC__ || defined __clang__
+ #define UNUSED __attribute__((unused))
++#else
++#define UNUSED
+ #endif
+ #define START_LEXER()           \
index e4ca56f282a65d7585ad3cf3fd2f20c3b207879f..325d7badcf94caced1d0f8639f324965df73316f 100644 (file)
@@ -572,6 +572,7 @@ dnl specific structure. We may need to change x-<lang>.c so that it works with
 dnl a newer version of tree-sitter-<lang>.
 . $srcdir/tree-sitter.cfg
 AC_SUBST([TREE_SITTER_VERSION])
+AC_SUBST([TREE_SITTER_GO_VERSION])
 AC_SUBST([TREE_SITTER_RUST_VERSION])
 
 PACKAGE_SUFFIX="-$ARCHIVE_VERSION"
index bc43d4b23f36e9d7504513e96848f92febd90ad2..8d975c6735eb895c948d46f552d690133e96cf25 100644 (file)
@@ -70,6 +70,7 @@ gettext_TEXINFOS = \
   lang-elisp.texi \
   lang-librep.texi \
   lang-rust.texi \
+  lang-go.texi \
   lang-ruby.texi \
   lang-sh.texi \
     $(top_srcdir)/../gettext-runtime/doc/rt-gettext.texi \
index 0fa7087ffb951a98a42f1f349b0f31fe0be17991..edc27dc7d8bde3634bf3e1809fa887329e923f45 100644 (file)
@@ -406,6 +406,7 @@ The Translator's View
 * elisp-format::                Emacs Lisp Format Strings
 * librep-format::               librep Format Strings
 * rust-format::                 Rust Format Strings
+* go-format::                   Go Format Strings
 * ruby-format::                 Ruby Format Strings
 * sh-format::                   Shell Format Strings
 * awk-format::                  awk Format Strings
@@ -437,6 +438,7 @@ Individual Programming Languages
 * Emacs Lisp::                  Emacs Lisp
 * librep::                      librep
 * Rust::                        Rust
+* Go::                          Go
 * Ruby::                        Ruby
 * sh::                          sh - Shell Script
 * bash::                        bash - Bourne-Again Shell Script
@@ -1712,6 +1714,12 @@ Likewise for librep, see @ref{librep-format}.
 @kwindex no-rust-format@r{ flag}
 Likewise for Rust, see @ref{rust-format}.
 
+@item go-format
+@kwindex go-format@r{ flag}
+@itemx no-go-format
+@kwindex no-go-format@r{ flag}
+Likewise for Go, see @ref{go-format}.
+
 @item ruby-format
 @kwindex ruby-format@r{ flag}
 @itemx no-ruby-format
@@ -2315,6 +2323,7 @@ at runtime (or possibly at compile time, if the compiler supports that).
 @cindex Java, string concatenation
 @cindex C#, string concatenation
 @cindex JavaScript, string concatenation
+@cindex Go, string concatenation
 @cindex Ruby, string concatenation
 @cindex Shell, string concatenation
 @cindex awk, string concatenation
@@ -2341,6 +2350,9 @@ In C#, string concatenation is denoted by the @samp{+} operator.
 In JavaScript, string concatenation is denoted by the @samp{+} operator.
 @c Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Addition
 @item
+In Go, string concatenation is denoted by the @samp{+} operator.
+@c Reference: https://go.dev/ref/spec#Arithmetic_operators
+@item
 In Ruby, string concatenation is denoted by the @samp{+} operator.
 @c Reference: https://en.wikibooks.org/wiki/Ruby_Programming/Syntax/Operators
 @c (Ignore ruby-doc.org! It is hopelessly outdated.)
@@ -9910,6 +9922,7 @@ strings.
 * elisp-format::                Emacs Lisp Format Strings
 * librep-format::               librep Format Strings
 * rust-format::                 Rust Format Strings
+* go-format::                   Go Format Strings
 * ruby-format::                 Ruby Format Strings
 * sh-format::                   Shell Format Strings
 * awk-format::                  awk Format Strings
@@ -10143,6 +10156,13 @@ a closing brace @samp{@}}.
 Brace characters @samp{@{} and @samp{@}} can be escaped by doubling them:
 @samp{@{@{} and @samp{@}@}}.
 
+@node go-format
+@subsection Go Format Strings
+
+Go format strings are documented
+on the Go packages site, for package @code{fmt},
+at @url{https://pkg.go.dev/fmt}.
+
 @node ruby-format
 @subsection Ruby Format Strings
 
@@ -10412,6 +10432,7 @@ that language, and to combine the resulting files using @code{msgcat}.
 * Emacs Lisp::                  Emacs Lisp
 * librep::                      librep
 * Rust::                        Rust
+* Go::                          Go
 * Ruby::                        Ruby
 * sh::                          sh - Shell Script
 * bash::                        bash - Bourne-Again Shell Script
@@ -10440,6 +10461,7 @@ that language, and to combine the resulting files using @code{msgcat}.
 @include lang-elisp.texi
 @include lang-librep.texi
 @include lang-rust.texi
+@include lang-go.texi
 @include lang-ruby.texi
 @include lang-sh.texi
 @include lang-bash.texi
diff --git a/gettext-tools/doc/lang-go.texi b/gettext-tools/doc/lang-go.texi
new file mode 100644 (file)
index 0000000..fe7ea2a
--- /dev/null
@@ -0,0 +1,251 @@
+@c This file is part of the GNU gettext manual.
+@c Copyright (C) 1995-2025 Free Software Foundation, Inc.
+@c See the file gettext.texi for copying conditions.
+
+@node Go
+@subsection Go
+@cindex Go
+
+@ignore
+Remarks about the Go language:
+
+The language is multithreaded and especially targeted at building web servers
+(see <https://en.wikipedia.org/wiki/Go_(programming_language)#Applications>).
+So, there are two use-cases:
+  - Single-locale programs (SL), possibly single-threaded or multi-threaded,
+    such as command-line programs,
+  - Multi-locale programs (ML), one locale per thread,
+    such as web servers, but also possibly GUI programs.
+Some of the localization packages provide API for SL and ML; others only
+support the SL case.
+Note that due to the lack of a "thread-local" variable type (like in C or Java),
+an API for the SL case cannot easily be used for the ML case. For the ML case,
+an explicitly object-oriented API is necessary (and e.g. in web servers,
+such objects can be stuffed into a "context" that is attached to the HTTP
+request).
+
+There are no locales; instead packages use
+  - either a locale string ("ll_CC" or in BCP 47 syntax),
+  - or the language 'Tag' data type
+    https://pkg.go.dev/golang.org/x/text/language
+@end ignore
+
+@ignore
+There are three general approaches for localization in Go.
+
+1) The semi-standard golang/x/text package.
+URLs: https://pkg.go.dev/golang.org/x/text
+      https://cs.opensource.google/go/x/text
+This is an internationalization framework that covers number formatting,
+collation, and more.
+
+It has a subpackage for localized messages:
+URL:  https://pkg.go.dev/golang.org/x/text/message
+However, this subpackage is an unfinished prototype:
+* It supports *only* the translation via a hash table in memory.
+  Translation via message catalogs as separate files is not supported!
+* https://pkg.go.dev/golang.org/x/text@v0.21.0/message/catalog
+  says "This package is UNDER CONSTRUCTION and its API may change."
+* https://github.com/golang/go/issues/63173
+  https://github.com/golang/go/issues/12750
+  https://github.com/golang/proposal/blob/master/design/12750-localization.md
+
+The message extractor here is 'gotext'.
+Installable through "go install golang.org/x/text/cmd/gotext".
+
+2) The nonstandard but widely used nicksnyder package.
+URLs: https://pkg.go.dev/github.com/nicksnyder/go-i18n/v2/i18n
+      https://github.com/nicksnyder/go-i18n
+Its advantages:
+  - Lots of flexibility for special cases.
+Its drawbacks:
+  - The code to use this is quite verbose: several lines of code for each message.
+  - Uses a programmer-assigned ID as key (defaults to "", not to the value of 'Other').
+    Thus, no PO files, and no support from GNU gettext is possible here.
+
+The message extractor here is 'goi18n'.
+Installable through "go install github.com/nicksnyder/go-i18n/goi18n".
+
+3) Various packages that provide for string to string translation.
+This is what we support.
+@end ignore
+
+Three packages are available,
+that can be used for message localization with PO files:
+@itemize @bullet
+@item
+The @code{github.com/leonelquinteros/gotext} package.
+
+Documentation: @url{https://pkg.go.dev/github.com/leonelquinteros/gotext}
+
+Source code: @url{https://github.com/leonelquinteros/gotext}
+@item
+The @code{github.com/gosexy/gettext} package.
+
+Documentation: @url{https://pkg.go.dev/github.com/gosexy/gettext}
+
+Source code: @url{https://github.com/gosexy/gettext}
+@item
+The @code{github.com/snapcore/go-gettext} package.
+
+Documentation: @url{https://pkg.go.dev/github.com/snapcore/go-gettext}
+
+Source code: @url{https://github.com/canonical/go-gettext}
+@end itemize
+
+Go programs can be classified as one of:
+@itemize @bullet
+@item
+@emph{Single-locale} programs,
+that use the same locale across all threads of the program.
+Example: Most command-line programs.
+@item
+@emph{Multi-locale} programs,
+that use one locale per thread.
+Example: Web servers.
+@end itemize
+
+The three different packages support these two classes of programs differently:
+@itemize @bullet
+@item
+@code{github.com/leonelquinteros/gotext} package:
+It has two different APIs,
+one for the single-locale case and one for the multi-locale case.
+@item
+@code{github.com/gosexy/gettext} package:
+Its API supports only the single-locale case.
+@item
+@code{github.com/snapcore/go-gettext} package:
+Its API supports the single-locale case and the multi-locale case in the same way.
+@end itemize
+
+@subheading Gettext support characteristics:
+
+@table @asis
+@item RPMs
+golang
+
+@item Ubuntu packages
+golang-go (which provides the @command{go} program),
+or gccgo (which provides a @command{go-@var{version}} command).@*
+gccgo has better portability; for example it works on SPARC CPUs.
+
+@item File extension
+@code{go}
+
+@item String syntax
+@code{"abc"}, @code{`abc`}
+
+@item gettext shorthand
+---
+
+@item gettext/ngettext functions
+This depends on the API:
+@itemize @bullet
+@item
+@code{github.com/leonelquinteros/gotext} API:
+@code{Get}, @code{GetD}, @code{GetN}, @code{GetND}
+@item
+@code{github.com/gosexy/gettext} API:
+@code{Gettext}, @code{DGettext}, @code{DCGettext}, @code{NGettext},
+@code{DNGettext}, @code{DCNGettext}
+@item
+@code{github.com/snapcore/go-gettext} API:
+@code{Gettext}, @code{NGettext}
+@end itemize
+
+Note that the @code{ngettext}-like functions need to take
+two argument strings that consume the same number of arguments.
+For example, you cannot write
+@code{fmt.Sprintf(gotext.GetN("a piece", "%d pieces", n), n)}
+because in the singular case,
+@code{fmt.Sprintf} would treat the unused argument as an error and
+produce @code{"a piece%!(EXTRA int=1)"} instead of the desired @code{"a piece"}.
+As a workaround, you need to convert @code{n} to a string and
+format that string with precision zero:
+@code{fmt.Sprintf(gotext.GetN("%.0sa piece", "%s pieces", n), strconv.Itoa(n))}
+
+@item textdomain
+This depends on the API:
+@itemize @bullet
+@item
+@code{github.com/leonelquinteros/gotext} API:
+@code{Locale.AddDomain} method or @code{gotext.Configure} function
+@item
+@code{github.com/gosexy/gettext} API:
+@code{Textdomain} function
+@item
+@code{github.com/snapcore/go-gettext} API:
+@code{TextDomain} constructor
+@end itemize
+
+@item bindtextdomain
+This depends on the API:
+@itemize @bullet
+@item
+@code{github.com/leonelquinteros/gotext} API:
+@code{gotext.NewLocale} function or @code{gotext.Configure} function
+@item
+@code{github.com/gosexy/gettext} API:
+@code{BindTextdomain} function
+@item
+@code{github.com/snapcore/go-gettext} API:
+@code{TextDomain} constructor
+@end itemize
+
+@item setlocale
+This depends on the API:
+@itemize @bullet
+@item
+@code{github.com/leonelquinteros/gotext} API:
+Programmer must determine the appropriate locale and pass it to the
+@code{gotext.NewLocale} function or @code{gotext.Configure} function.
+@item
+@code{github.com/gosexy/gettext} API:
+Programmer must call @code{gettext.SetLocale(gettext.LcAll, "")}.
+@item
+@code{github.com/snapcore/go-gettext} API:
+Programmer must determine the appropriate locale and pass it to the
+@code{TextDomain.Locale} method.
+@end itemize
+
+@item Prerequisite
+This depends on the API:
+@itemize @bullet
+@item
+@code{github.com/leonelquinteros/gotext} API:
+@code{import ("github.com/leonelquinteros/gotext")}
+@item
+@code{github.com/gosexy/gettext} API:
+@code{import ("github.com/gosexy/gettext")}
+@item
+@code{github.com/snapcore/go-gettext} API:
+@code{import ("github.com/snapcore/go-gettext")}
+@end itemize
+
+@item Use or emulate GNU gettext
+This depends on the API:
+@itemize @bullet
+@item
+@code{github.com/leonelquinteros/gotext} API:
+Emulate
+@item
+@code{github.com/gosexy/gettext} API:
+Use
+@item
+@code{github.com/snapcore/go-gettext} API:
+Emulate
+@end itemize
+
+@item Extractor
+@code{xgettext}
+
+@item Formatting with positions
+@code{fmt.Sprintf("%[2]d %[1]d", ...)}
+
+@item Portability
+fully portable
+
+@item po-mode marking
+---
+@end table
index 21306489f9c51da0e4be0b05cf383485fa8190eb..b6b4e980123cb1d51edd4a378c306b1d4414acef 100644 (file)
@@ -84,6 +84,7 @@ Specifies the language of the input files.  The supported languages are
 @code{EmacsLisp},
 @code{librep},
 @code{Rust},
+@code{Go},
 @code{Ruby},
 @code{Shell},
 @code{awk},
@@ -256,6 +257,7 @@ Lisp,
 EmacsLisp,
 librep,
 Rust,
+Go,
 Shell,
 awk,
 Lua,
@@ -317,6 +319,7 @@ Lisp,
 EmacsLisp,
 librep,
 Rust,
+Go,
 Shell,
 awk,
 Lua,
@@ -473,6 +476,7 @@ Lisp,
 EmacsLisp,
 librep,
 Rust,
+Go,
 Shell,
 awk,
 Lua,
index 85e32df220ff158e541cca93f5cfbf1a27c426a1..25731ecd02bd47071d7ad6d10fbc41df9d89d3f9 100644 (file)
@@ -80,6 +80,7 @@ libgettextpo_la_AUXSOURCES = \
   ../src/format-elisp.c \
   ../src/format-librep.c \
   ../src/format-rust.c \
+  ../src/format-go.c \
   ../src/format-ruby.c \
   ../src/format-sh.c \
   ../src/format-awk.c \
index 554667952837d1e92d2f8d71433b2ee98b285f51..e7f99d19f12518e3f741d459915d8a3dd222ea67 100644 (file)
@@ -237,6 +237,7 @@ format-lisp.c          Format string handling for Common Lisp.
 format-elisp.c         Format string handling for Emacs Lisp.
 format-librep.c        Format string handling for librep.
 format-rust.c          Format string handling for Rust.
+format-go.c            Format string handling for Go.
 format-ruby.c          Format string handling for Ruby.
 format-sh.c            Format string handling for Shell.
 format-awk.c           Format string handling for awk.
@@ -388,6 +389,9 @@ msgl-check.c
 | x-rust.h
 | x-rust.c
 |               String extractor for Rust.
+| x-go.h
+| x-go.c
+|               String extractor for Go.
 | x-ruby.h
 | x-ruby.c
 |               String extractor for Ruby.
index 27d234092938fa5eb4ab0c25ebb9d40fe3300601..12285c3dc62e57ce2ffb566be4f7530aa0da7437 100644 (file)
@@ -81,6 +81,7 @@ noinst_HEADERS = \
   x-elisp.h \
   x-librep.h \
   x-rust.h \
+  x-go.h \
   x-ruby.h \
   x-sh.h \
   x-awk.h \
@@ -194,6 +195,7 @@ FORMAT_SOURCE += \
   format-elisp.c \
   format-librep.c \
   format-rust.c \
+  format-go.c \
   format-ruby.c \
   format-sh.c \
   format-awk.c \
@@ -246,7 +248,8 @@ libxgettextts1_a_CPPFLAGS = \
   -I$(top_srcdir)/tree-sitter-$(TREE_SITTER_VERSION)/lib/src
 libxgettextts2_a_SOURCES = \
   ../tree-sitter-rust-$(TREE_SITTER_RUST_VERSION)/src/rust-parser.c \
-  ../tree-sitter-rust-$(TREE_SITTER_RUST_VERSION)/src/rust-scanner.c
+  ../tree-sitter-rust-$(TREE_SITTER_RUST_VERSION)/src/rust-scanner.c \
+  ../tree-sitter-go-$(TREE_SITTER_GO_VERSION)/src/go-parser.c
 libxgettextts2_a_CPPFLAGS = \
   -I$(top_srcdir)/tree-sitter-$(TREE_SITTER_VERSION)/lib/include
 
@@ -296,6 +299,7 @@ xgettext_SOURCES += \
   x-elisp.c \
   x-librep.c \
   x-rust.c \
+  x-go.c \
   x-ruby.c \
   x-sh.c ../../gettext-runtime/src/escapes.h \
   x-awk.c \
diff --git a/gettext-tools/src/format-go.c b/gettext-tools/src/format-go.c
new file mode 100644 (file)
index 0000000..ae651af
--- /dev/null
@@ -0,0 +1,730 @@
+/* Go format strings.
+   Copyright (C) 2001-2025 Free Software Foundation, Inc.
+   Written by Bruno Haible <haible@clisp.cons.org>, 2025.
+
+   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 <https://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdbool.h>
+#include <stdlib.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)
+
+/* Go format strings are described in
+   https://pkg.go.dev/fmt
+   and are implemented in gcc-14.2.0/libgo/go/fmt/print.go .
+   A format string consists of literal text and directives.
+   A directive
+   - starts with '%',
+   - is optionally followed by any of the characters '#', '0', '-', ' ', '+',
+     each of which acts as a flag,
+   - is optionally followed by a width specification: '*' (reads an argument)
+     or '[m]*' (moves to the m-th argument and reads that argument) or a digit
+     sequence with value <= 1000000,
+   - is optionally followed by '.' and a precision specification: '*' (reads an
+     argument) or '[m]*' (moves to the m-th argument and reads that argument) or
+     a digit sequence with value <= 1000000,
+   - is optionally followed by '[m]', where m is a digit sequence with a
+     positive value <= 1000000,
+   - is finished by a specifier
+       - '%', that needs no argument,
+       - 'v', that need a generic value argument,
+       - 'T', that need a generic value argument to take the type of,
+       - 't', that need a boolean argument,
+       - 'c', that need a character argument,
+       - 'U', that need a character argument to print with U+nnnn escapes,
+       - 's', that need a string argument,
+       - 'q', that need a character or string argument to print as a
+         character-literal or string-literal, respectively, with escapes,
+       - 'e', 'E', 'f', 'F', 'g', 'G', that need a floating-point argument,
+       - 'O', that need an integer argument,
+       - 'd', 'o', that need an integer or pointer argument,
+       - 'b', that need an integer or floating-point or pointer argument,
+       - 'x', 'X', that need an integer or floating-point or string or pointer
+         argument.
+   Numbered ('[m]' or '[m]*') and unnumbered argument specifications can be
+   used in the same string.  The effect of '[m]' is to set the current argument
+   number to m.  The current argument number is incremented after processing an
+   argument.
+ */
+
+enum format_arg_type
+{
+  FAT_NONE           = 0,
+  FAT_BOOLEAN        = 1 << 0,
+  FAT_CHARACTER      = 1 << 1,
+  FAT_STRING         = 1 << 2,
+  FAT_FLOATINGPOINT  = 1 << 3,
+  FAT_INTEGER        = 1 << 4,
+  FAT_POINTER        = 1 << 5,
+  FAT_ANYVALUE       = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3)
+                       | (1 << 4) | (1 << 5) | (1 << 6),
+  FAT_ANYVALUE_TYPE  = 1 << 7
+};
+typedef unsigned int format_arg_type_t;
+
+struct numbered_arg
+{
+  unsigned int number;
+  format_arg_type_t type;
+};
+
+struct spec
+{
+  unsigned int directives;
+  unsigned int numbered_arg_count;
+  struct numbered_arg *numbered;
+};
+
+/* Locale independent test for a decimal digit.
+   Argument can be  'char' or 'unsigned char'.  (Whereas the argument of
+   <ctype.h> isdigit must be an 'unsigned char'.)  */
+#undef isdigit
+#define isdigit(c) ((unsigned int) ((c) - '0') < 10)
+
+
+static int
+numbered_arg_compare (const void *p1, const void *p2)
+{
+  unsigned int n1 = ((const struct numbered_arg *) p1)->number;
+  unsigned int n2 = ((const struct numbered_arg *) p2)->number;
+
+  return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0);
+}
+
+#define INVALID_ARGNO_TOO_LARGE(directive_number) \
+  xasprintf (_("In the directive number %u, the argument number is too large."), directive_number)
+
+static void *
+format_parse (const char *format, bool translated, char *fdi,
+              char **invalid_reason)
+{
+  const char *const format_start = format;
+  struct spec spec;
+  unsigned int allocated;
+  struct spec *result;
+  unsigned int number;
+
+  spec.directives = 0;
+  spec.numbered_arg_count = 0;
+  spec.numbered = NULL;
+  allocated = 0;
+  number = 1;
+
+  for (; *format != '\0';)
+    if (*format++ == '%')
+      {
+        /* A directive.  */
+        enum format_arg_type type;
+
+        FDI_SET (format - 1, FMTDIR_START);
+        spec.directives++;
+
+        /* Parse flags.  */
+        while (*format == ' ' || *format == '+' || *format == '-'
+               || *format == '#' || *format == '0')
+          format++;
+
+        if (*format == '[')
+          {
+            if (isdigit (format[1]))
+              {
+                const char *f = format + 1;
+                unsigned int m = 0;
+
+                do
+                  {
+                    if (m <= 1000000)
+                      m = 10 * m + (*f - '0');
+                    f++;
+                  }
+                while (isdigit (*f));
+
+                if (*f == ']')
+                  {
+                    if (m == 0)
+                      {
+                        *invalid_reason = INVALID_ARGNO_0 (spec.directives);
+                        FDI_SET (f, FMTDIR_ERROR);
+                        goto bad_format;
+                      }
+                    if (m > 1000000)
+                      {
+                        *invalid_reason = INVALID_ARGNO_TOO_LARGE (spec.directives);
+                        FDI_SET (f, FMTDIR_ERROR);
+                        goto bad_format;
+                      }
+                    number = m;
+                    format = ++f;
+
+                    /* Parse width.  */
+                    if (*format == '*')
+                      {
+                        unsigned int width_number = number;
+
+                        if (allocated == spec.numbered_arg_count)
+                          {
+                            allocated = 2 * allocated + 1;
+                            spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, allocated * sizeof (struct numbered_arg));
+                          }
+                        spec.numbered[spec.numbered_arg_count].number = width_number;
+                        spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER;
+                        spec.numbered_arg_count++;
+
+                        number++;
+                        format++;
+                        goto parse_precision;
+                      }
+
+                    goto parse_specifier;
+                  }
+              }
+          }
+
+        /* Parse width other than [m]*.  */
+        if (isdigit (*format))
+          {
+            const char *f = format;
+            unsigned int width = 0;
+
+            do
+              {
+                if (width <= 1000000)
+                  width = 10 * width + (*f - '0');
+                f++;
+              }
+            while (isdigit (*f));
+
+            if (width > 1000000)
+              {
+                *invalid_reason =
+                  xasprintf (_("In the directive number %u, the width is too large."),
+                             spec.directives);
+                FDI_SET (f - 1, FMTDIR_ERROR);
+                goto bad_format;
+              }
+            format = f;
+          }
+        else if (*format == '*')
+          {
+            unsigned int width_number = number;
+
+            if (allocated == spec.numbered_arg_count)
+              {
+                allocated = 2 * allocated + 1;
+                spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, allocated * sizeof (struct numbered_arg));
+              }
+            spec.numbered[spec.numbered_arg_count].number = width_number;
+            spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER;
+            spec.numbered_arg_count++;
+
+            number++;
+            format++;
+          }
+
+       parse_precision:
+        /* Parse precision.  */
+        if (*format == '.')
+          {
+            if (format[1] == '[')
+              {
+                if (isdigit (format[2]))
+                  {
+                    const char *f = format + 2;
+                    unsigned int m = 0;
+
+                    do
+                      {
+                        if (m <= 1000000)
+                          m = 10 * m + (*f - '0');
+                        f++;
+                      }
+                    while (isdigit (*f));
+
+                    if (*f == ']')
+                      {
+                        if (m == 0)
+                          {
+                            *invalid_reason = INVALID_ARGNO_0 (spec.directives);
+                            FDI_SET (f, FMTDIR_ERROR);
+                            goto bad_format;
+                          }
+                        if (m > 1000000)
+                          {
+                            *invalid_reason = INVALID_ARGNO_TOO_LARGE (spec.directives);
+                            FDI_SET (f, FMTDIR_ERROR);
+                            goto bad_format;
+                          }
+                        number = m;
+                        format = ++f;
+
+                        /* Finish parsing precision.  */
+                        if (*format == '*')
+                          {
+                            unsigned int precision_number = number;
+
+                            if (allocated == spec.numbered_arg_count)
+                              {
+                                allocated = 2 * allocated + 1;
+                                spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, allocated * sizeof (struct numbered_arg));
+                              }
+                            spec.numbered[spec.numbered_arg_count].number = precision_number;
+                            spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER;
+                            spec.numbered_arg_count++;
+
+                            number++;
+                            format++;
+                            goto parse_value;
+                          }
+
+                        goto parse_specifier;
+                      }
+
+                    /* The precision was empty, means zero.  */
+                    goto parse_specifier;
+                  }
+              }
+
+            /* Parse precision other than [m]*.  */
+            if (isdigit (format[1]))
+              {
+                const char *f = format + 1;
+                unsigned int precision = 0;
+
+                do
+                  {
+                    if (precision <= 1000000)
+                      precision = 10 * precision + (*f - '0');
+                    f++;
+                  }
+                while (isdigit (*f));
+
+                if (precision > 1000000)
+                  {
+                    *invalid_reason =
+                      xasprintf (_("In the directive number %u, the precision is too large."),
+                                 spec.directives);
+                    FDI_SET (f - 1, FMTDIR_ERROR);
+                    goto bad_format;
+                  }
+                format = f;
+              }
+            else if (format[1] == '*')
+              {
+                unsigned int precision_number = number;
+
+                if (allocated == spec.numbered_arg_count)
+                  {
+                    allocated = 2 * allocated + 1;
+                    spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, allocated * sizeof (struct numbered_arg));
+                  }
+                spec.numbered[spec.numbered_arg_count].number = precision_number;
+                spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER;
+                spec.numbered_arg_count++;
+
+                number++;
+                format += 2;
+              }
+          }
+
+       parse_value:
+        if (*format == '[')
+          {
+            if (isdigit (format[1]))
+              {
+                const char *f = format + 1;
+                unsigned int m = 0;
+
+                do
+                  {
+                    if (m <= 1000000)
+                      m = 10 * m + (*f - '0');
+                    f++;
+                  }
+                while (isdigit (*f));
+
+                if (*f == ']')
+                  {
+                    if (m == 0)
+                      {
+                        *invalid_reason = INVALID_ARGNO_0 (spec.directives);
+                        FDI_SET (f, FMTDIR_ERROR);
+                        goto bad_format;
+                      }
+                    if (m > 1000000)
+                      {
+                        *invalid_reason = INVALID_ARGNO_TOO_LARGE (spec.directives);
+                        FDI_SET (f, FMTDIR_ERROR);
+                        goto bad_format;
+                      }
+                    number = m;
+                    format = ++f;
+                    goto parse_specifier;
+                  }
+              }
+          }
+
+       parse_specifier:
+        /* Parse the specifier.  */
+        switch (*format)
+          {
+          case '%':
+            type = FAT_NONE;
+            break;
+          case 'v':
+            type = FAT_ANYVALUE;
+            break;
+          case 'T':
+            type = FAT_ANYVALUE_TYPE;
+            break;
+          case 't':
+            type = FAT_BOOLEAN;
+            break;
+          case 'c':
+          case 'U':
+            type = FAT_CHARACTER;
+            break;
+          case 's':
+            type = FAT_STRING;
+            break;
+          case 'q':
+            type = FAT_CHARACTER | FAT_STRING;
+            break;
+          case 'e': case 'E':
+          case 'f': case 'F':
+          case 'g': case 'G':
+            type = FAT_FLOATINGPOINT;
+            break;
+          case 'O':
+            type = FAT_INTEGER;
+            break;
+          case 'd':
+          case 'o':
+            type = FAT_INTEGER | FAT_POINTER;
+            break;
+          case 'b':
+            type = FAT_INTEGER | FAT_FLOATINGPOINT | FAT_POINTER;
+            break;
+          case 'x': case 'X':
+            type = FAT_INTEGER | FAT_FLOATINGPOINT | FAT_STRING | FAT_POINTER;
+            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 (type != FAT_NONE)
+          {
+            if (allocated == spec.numbered_arg_count)
+              {
+                allocated = 2 * allocated + 1;
+                spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, allocated * sizeof (struct numbered_arg));
+              }
+            spec.numbered[spec.numbered_arg_count].number = number;
+            spec.numbered[spec.numbered_arg_count].type = type;
+            spec.numbered_arg_count++;
+
+            number++;
+          }
+
+        FDI_SET (format, FMTDIR_END);
+
+        format++;
+      }
+
+  /* Sort the numbered argument array, and eliminate duplicates.  */
+  if (spec.numbered_arg_count > 1)
+    {
+      unsigned int i, j;
+      bool err;
+
+      qsort (spec.numbered, spec.numbered_arg_count,
+             sizeof (struct numbered_arg), numbered_arg_compare);
+
+      /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i.  */
+      err = false;
+      for (i = j = 0; i < spec.numbered_arg_count; i++)
+        if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number)
+          {
+            format_arg_type_t type1 = spec.numbered[i].type;
+            format_arg_type_t type2 = spec.numbered[j-1].type;
+            format_arg_type_t type_both = type1 & type2;
+
+            if (type_both == FAT_NONE)
+              {
+                /* Incompatible types.  */
+                if (!err)
+                  *invalid_reason =
+                    INVALID_INCOMPATIBLE_ARG_TYPES (spec.numbered[i].number);
+                err = true;
+              }
+
+            spec.numbered[j-1].type = type_both;
+          }
+        else
+          {
+            if (j < i)
+              {
+                spec.numbered[j].number = spec.numbered[i].number;
+                spec.numbered[j].type = spec.numbered[i].type;
+              }
+            j++;
+          }
+      spec.numbered_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.numbered != NULL)
+    free (spec.numbered);
+  return NULL;
+}
+
+static void
+format_free (void *descr)
+{
+  struct spec *spec = (struct spec *) descr;
+
+  if (spec->numbered != NULL)
+    free (spec->numbered);
+  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, void *error_logger_data,
+              const char *pretty_msgid, const char *pretty_msgstr)
+{
+  struct spec *spec1 = (struct spec *) msgid_descr;
+  struct spec *spec2 = (struct spec *) msgstr_descr;
+
+  /* The formatting functions in the Go package "fmt" treat an unused argument
+     as an error.  Therefore here the translator must not omit some of the
+     arguments.  */
+  equality = true;
+
+  bool err = false;
+
+  if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0)
+    {
+      unsigned int i, j;
+      unsigned int n1 = spec1->numbered_arg_count;
+      unsigned int n2 = spec2->numbered_arg_count;
+
+      /* Check that the argument numbers 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 :
+                     spec1->numbered[i].number > spec2->numbered[j].number ? 1 :
+                     spec1->numbered[i].number < spec2->numbered[j].number ? -1 :
+                     0);
+
+          if (cmp > 0)
+            {
+              if (error_logger)
+                error_logger (error_logger_data,
+                              _("a format specification for argument %u, as in '%s', doesn't exist in '%s'"),
+                              spec2->numbered[j].number, pretty_msgstr,
+                              pretty_msgid);
+              err = true;
+              break;
+            }
+          else if (cmp < 0)
+            {
+              if (equality)
+                {
+                  if (error_logger)
+                    error_logger (error_logger_data,
+                                  _("a format specification for argument %u doesn't exist in '%s'"),
+                                  spec1->numbered[i].number, 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 (spec1->numbered[i].number == spec2->numbered[j].number)
+              {
+                if (spec1->numbered[i].type != spec2->numbered[j].type)
+                  {
+                    if (error_logger)
+                      error_logger (error_logger_data,
+                                    _("format specifications in '%s' and '%s' for argument %u are not the same"),
+                                    pretty_msgid, pretty_msgstr,
+                                    spec2->numbered[j].number);
+                    err = true;
+                    break;
+                  }
+                j++, i++;
+              }
+            else
+              i++;
+          }
+    }
+
+  return err;
+}
+
+
+struct formatstring_parser formatstring_go =
+{
+  format_parse,
+  format_free,
+  format_get_number_of_directives,
+  NULL,
+  format_check
+};
+
+
+#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 last;
+  unsigned int i;
+
+  if (spec == NULL)
+    {
+      printf ("INVALID");
+      return;
+    }
+
+  printf ("(");
+  last = 1;
+  for (i = 0; i < spec->numbered_arg_count; i++)
+    {
+      unsigned int number = spec->numbered[i].number;
+
+      if (i > 0)
+        printf (" ");
+      if (number < last)
+        abort ();
+      for (; last < number; last++)
+        printf ("_ ");
+
+      format_arg_type_t type = spec->numbered[i].type;
+      if (type == FAT_NONE)
+        abort ();
+      if ((type & FAT_BOOLEAN) != 0)
+        printf ("b");
+      if ((type & FAT_CHARACTER) != 0)
+        printf ("c");
+      if ((type & FAT_STRING) != 0)
+        printf ("s");
+      if ((type & FAT_FLOATINGPOINT) != 0)
+        printf ("f");
+      if ((type & FAT_INTEGER) != 0)
+        printf ("i");
+      if ((type & FAT_POINTER) != 0)
+        printf ("p");
+      if ((type & (1 << 6)) != 0)
+        printf ("*");
+      if ((type & (1 << 7)) != 0)
+        printf ("T");
+
+      last = number + 1;
+    }
+  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../../gettext-runtime/intl -DHAVE_CONFIG_H -DTEST format-go.c ../gnulib-lib/libgettextlib.la"
+ * End:
+ */
+
+#endif /* TEST */
index ff5f56d261ec4db13173a2277474a4cbebda8799..e713586ebfb01301fff39af8fccf0860ddfa532b 100644 (file)
@@ -48,6 +48,7 @@ struct formatstring_parser *formatstring_parsers[NFORMATS] =
   /* format_elisp */            &formatstring_elisp,
   /* format_librep */           &formatstring_librep,
   /* format_rust */             &formatstring_rust,
+  /* format_go */               &formatstring_go,
   /* format_ruby */             &formatstring_ruby,
   /* format_sh */               &formatstring_sh,
   /* format_awk */              &formatstring_awk,
index fe0199b8a2381de4789139ebfe16f6991790eb45..1634bfbbf59bf32b812fce3fff671f7d1453421c 100644 (file)
@@ -114,6 +114,7 @@ extern DLL_VARIABLE struct formatstring_parser formatstring_lisp;
 extern DLL_VARIABLE struct formatstring_parser formatstring_elisp;
 extern DLL_VARIABLE struct formatstring_parser formatstring_librep;
 extern DLL_VARIABLE struct formatstring_parser formatstring_rust;
+extern DLL_VARIABLE struct formatstring_parser formatstring_go;
 extern DLL_VARIABLE struct formatstring_parser formatstring_ruby;
 extern DLL_VARIABLE struct formatstring_parser formatstring_sh;
 extern DLL_VARIABLE struct formatstring_parser formatstring_awk;
index ea1f61c029ac43a1a9b627883d62593a961aae36..2c6d94f5bb3dfecf830d5201ebcd8f192b5ca5fc 100644 (file)
@@ -48,6 +48,7 @@ const char *const format_language[NFORMATS] =
   /* format_elisp */            "elisp",
   /* format_librep */           "librep",
   /* format_rust */             "rust",
+  /* format_go */               "go",
   /* format_ruby */             "ruby",
   /* format_sh */               "sh",
   /* format_awk */              "awk",
@@ -84,6 +85,7 @@ const char *const format_language_pretty[NFORMATS] =
   /* format_elisp */            "Emacs Lisp",
   /* format_librep */           "librep",
   /* format_rust */             "Rust",
+  /* format_go */               "Go",
   /* format_ruby */             "Ruby",
   /* format_sh */               "Shell",
   /* format_awk */              "awk",
index c7c9699ff21b6beb391d46934b7c5994fc0da843..dd9447d6447a9193e934bcd2a151748b137871b8 100644 (file)
@@ -57,6 +57,7 @@ enum format_type
   format_elisp,
   format_librep,
   format_rust,
+  format_go,
   format_ruby,
   format_sh,
   format_awk,
@@ -76,7 +77,7 @@ enum format_type
   format_gfc_internal,
   format_ycp
 };
-#define NFORMATS 32     /* Number of format_type enum values.  */
+#define NFORMATS 33     /* 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 a/gettext-tools/src/x-go.c b/gettext-tools/src/x-go.c
new file mode 100644 (file)
index 0000000..4a02619
--- /dev/null
@@ -0,0 +1,3710 @@
+/* xgettext Go backend.
+   Copyright (C) 2001-2025 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 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 <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <bruno@clisp.org>, 2025.  */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+/* Specification.  */
+#include "x-go.h"
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define SBR_NO_PREPENDF
+#include <error.h>
+#include "message.h"
+#include "string-desc.h"
+#include "xstring-desc.h"
+#include "string-buffer-reversed.h"
+#include "xgettext.h"
+#include "xg-pos.h"
+#include "xg-mixed-string.h"
+#include "xg-arglist-context.h"
+#include "xg-arglist-callshape.h"
+#include "xg-arglist-parser.h"
+#include "xg-message.h"
+#include "if-error.h"
+#include "xalloc.h"
+#include "str-list.h"
+#include "mem-hash-map.h"
+#include "gl_map.h"
+#include "gl_xmap.h"
+#include "gl_hash_map.h"
+#include "gl_list.h"
+#include "gl_xlist.h"
+#include "gl_carray_list.h"
+#include "gl_set.h"
+#include "gl_xset.h"
+#include "gl_hash_set.h"
+#include "read-file.h"
+#include "unistr.h"
+#include "po-charset.h"
+#include "gettext.h"
+
+#define _(s) gettext(s)
+
+/* Use tree-sitter.
+   Documentation: <https://tree-sitter.github.io/tree-sitter/using-parsers>  */
+#include <tree_sitter/api.h>
+extern const TSLanguage *tree_sitter_go (void);
+
+
+/* The Go syntax is defined in https://go.dev/ref/spec.
+   String syntax:
+   https://go.dev/ref/spec#String_literals
+ */
+
+#define DEBUG_GO 0
+
+
+/* ==================== Preparing for Go type analysis. ==================== */
+
+/* We use Go type analysis, because the most heavily used Go package that
+   implements internationalization with PO files is
+     github.com/leonelquinteros/gotext
+   with a multi-locale API that uses a variable, such as
+
+        localizer := gotext.NewLocale(localedir, language)
+        ....
+        fmt.Println(localizer.Get("Hello, world!"))
+
+   or
+
+    var localizer_table map[string]*gotext.Locale
+    ...
+        localizer := localizer_table[lang]
+        ...
+        fmt.Fprintln(w, localizer.Get("Hello world!"))
+
+   or
+
+        localizer := r.Context().Value(localizerKey).(*gotext.Locale)
+        ...
+        fmt.Fprintln(w, localizer.Get("Hello world!"))
+
+   and because there are many other Get methods, unrelated to
+   internationalization, whose string argument should not be extracted:
+
+        x.Get("key")
+
+   where x is of type url.Values, http.Header, http.Client, and many more.  */
+
+enum go_type_e
+{
+  unknown          = 0,
+  /* bool
+     uint8 uint16 uint32 uint64
+     int8 int16 int32 int64
+     float32 float64
+     complex64 complex128
+     byte rune
+     uint int uintptr
+     string
+     error
+     comparable
+     any  */
+  predeclared      = 1,
+  /* pointer type:  *eltype  */
+  pointer          = 2,
+  /* array type:    [N]eltype
+     or slice type: []eltype  */
+  array            = 3,
+  /* map type:  map[keytype]eltype  */
+  map              = 4,
+  /* function type:  func(...) rettype
+     or with multiple values:  func(...) (rettype1, ..., rettypeN)  */
+  function         = 5,
+  /* struct type  */
+  go_struct        = 6,
+  /* interface type  */
+  go_interface     = 7,
+  /* channel type  */
+  channel          = 8,
+  /* other type */
+  other            = 9,
+  /* indirection to a named type (only during construction)  */
+  indirection      = 10
+};
+
+/* This struct represents the relevant parts (for type analysis) of a
+   Go type.  */
+struct go_type
+{
+  enum go_type_e e;
+  union
+  {
+    /* For pointer, array, map:  */
+    struct go_type *eltype;
+    /* For function:  */
+    struct
+    {
+      unsigned int n_values;
+      struct go_type **values;
+    } function_def;
+    /* For struct:  */
+    struct
+    {
+      unsigned int n_members;
+      struct go_struct_member { const char *name; struct go_type *type; }
+        *members;
+      unsigned int n_methods;
+      unsigned int n_methods_allocated;
+      struct go_struct_method { const char *name; struct go_type *type; }
+        *methods;
+    } struct_def;
+    /* For interface:  */
+    struct
+    {
+      unsigned int n_methods;
+      struct go_interface_member { const char *name; struct go_type *type; }
+        *methods;
+      unsigned int n_interfaces;
+      struct go_type **interfaces;
+    } interface_def;
+    /* For indirection:  */
+    const char *type_name;
+  } u;
+};
+
+typedef struct go_type go_type_t;
+
+/* Pre-built 'struct go_type' objects.  */
+static go_type_t unknown_type = { unknown, { NULL } };
+static go_type_t a_predeclared_type = { predeclared, { NULL } };
+MAYBE_UNUSED static go_type_t a_channel_type = { channel, { NULL } };
+static go_type_t another_type = { other, { NULL } };
+
+/* Construction of 'struct go_type' objects.  */
+
+static go_type_t *
+create_pointer_type (go_type_t *eltype)
+{
+  go_type_t *result = XMALLOC (struct go_type);
+  result->e = pointer;
+  result->u.eltype = eltype;
+  return result;
+}
+
+static go_type_t *
+create_array_type (go_type_t *eltype)
+{
+  go_type_t *result = XMALLOC (struct go_type);
+  result->e = array;
+  result->u.eltype = eltype;
+  return result;
+}
+
+static go_type_t *
+create_map_type (go_type_t *eltype)
+{
+  go_type_t *result = XMALLOC (struct go_type);
+  result->e = map;
+  result->u.eltype = eltype;
+  return result;
+}
+
+static go_type_t *
+create_function_type (unsigned int n_values, struct go_type *values[])
+{
+  go_type_t *result = XMALLOC (struct go_type);
+  go_type_t **heap_values = XNMALLOC (n_values, go_type_t *);
+  unsigned int i;
+
+  for (i = 0; i < n_values; i++)
+    heap_values[i] = values[i];
+  result->e = function;
+  result->u.function_def.n_values = n_values;
+  result->u.function_def.values = heap_values;
+  return result;
+}
+
+static go_type_t *
+create_struct_type (unsigned int n_members, struct go_struct_member members[])
+{
+  go_type_t *result = XMALLOC (struct go_type);
+  struct go_struct_member *heap_members =
+    XNMALLOC (n_members, struct go_struct_member);
+  unsigned int i;
+
+  for (i = 0; i < n_members; i++)
+    heap_members[i] = members[i];
+  result->e = go_struct;
+  result->u.struct_def.n_members = n_members;
+  result->u.struct_def.members = heap_members;
+  result->u.struct_def.n_methods = 0;
+  result->u.struct_def.n_methods_allocated = 0;
+  result->u.struct_def.methods = NULL;
+  return result;
+}
+
+static go_type_t *
+create_interface_type (unsigned int n_methods, struct go_interface_member methods[],
+                       unsigned int n_interfaces, struct go_type *interfaces[])
+{
+  go_type_t *result = XMALLOC (struct go_type);
+  struct go_interface_member *heap_methods =
+    XNMALLOC (n_methods, struct go_interface_member);
+  struct go_type **heap_interfaces = XNMALLOC (n_interfaces, struct go_type *);
+  unsigned int i;
+
+  for (i = 0; i < n_methods; i++)
+    heap_methods[i] = methods[i];
+  for (i = 0; i < n_interfaces; i++)
+    heap_interfaces[i] = interfaces[i];
+  result->e = go_interface;
+  result->u.interface_def.n_methods = n_methods;
+  result->u.interface_def.methods = heap_methods;
+  result->u.interface_def.n_interfaces = n_interfaces;
+  result->u.interface_def.interfaces = heap_interfaces;
+  return result;
+}
+
+static go_type_t *
+create_other_type (const char *name)
+{
+  (void) name;
+  return &another_type;
+}
+
+#if DEBUG_GO
+/* Print a type in a somewhat readable way.  */
+static void
+print_type_recurse (go_type_t *type, int maxdepth, FILE *fp)
+{
+  if (maxdepth == 0)
+    {
+      fprintf (fp, "...");
+      return;
+    }
+  maxdepth--;
+  switch (type->e)
+    {
+    case unknown:
+      fprintf (fp, "unknown");
+      break;
+    case predeclared:
+      fprintf (fp, "predeclared");
+      break;
+    case pointer:
+      fprintf (fp, "*");
+      print_type_recurse (type->u.eltype, maxdepth, fp);
+      break;
+    case array:
+      fprintf (fp, "[]");
+      print_type_recurse (type->u.eltype, maxdepth, fp);
+      break;
+    case map:
+      fprintf (fp, "map[...]");
+      print_type_recurse (type->u.eltype, maxdepth, fp);
+      break;
+    case function:
+      fprintf (fp, "func(...) ");
+      if (type->u.function_def.n_values == 1)
+        print_type_recurse (type->u.function_def.values[0], maxdepth, fp);
+      else
+        {
+          unsigned int i;
+          fprintf (fp, "(");
+          for (i = 0; i < type->u.function_def.n_values; i++)
+            {
+              if (i > 0)
+                fprintf (fp, ", ");
+              print_type_recurse (type->u.function_def.values[i], maxdepth, fp);
+            }
+          fprintf (fp, ")");
+        }
+      break;
+    case go_struct:
+      {
+        unsigned int i;
+        fprintf (fp, "struct {\n");
+        for (i = 0; i < type->u.struct_def.n_members; i++)
+          {
+            fprintf (fp, "  %s ", type->u.struct_def.members[i].name);
+            print_type_recurse (type->u.struct_def.members[i].type, maxdepth, fp);
+            fprintf (fp, ";\n");
+          }
+        fprintf (fp, "  -- methods:\n");
+        for (i = 0; i < type->u.struct_def.n_methods; i++)
+          {
+            fprintf (fp, "  %s ", type->u.struct_def.methods[i].name);
+            print_type_recurse (type->u.struct_def.methods[i].type, maxdepth, fp);
+            fprintf (fp, ";\n");
+          }
+        fprintf (fp, "}\n");
+      }
+      break;
+    case go_interface:
+      {
+        unsigned int i;
+        fprintf (fp, "interface {\n");
+        for (i = 0; i < type->u.interface_def.n_methods; i++)
+          {
+            fprintf (fp, "  %s ", type->u.interface_def.methods[i].name);
+            print_type_recurse (type->u.interface_def.methods[i].type, maxdepth, fp);
+            fprintf (fp, ";\n");
+          }
+        fprintf (fp, "  -- interfaces:\n");
+        for (i = 0; i < type->u.interface_def.n_interfaces; i++)
+          {
+            fprintf (fp, "  ");
+            print_type_recurse (type->u.interface_def.interfaces[i], maxdepth, fp);
+            fprintf (fp, ";\n");
+          }
+        fprintf (fp, "}\n");
+      }
+      break;
+    case channel:
+      fprintf (fp, "channel");
+      break;
+    case other:
+      fprintf (fp, "other");
+      break;
+    default:
+      abort ();
+    }
+}
+static void
+print_type (go_type_t *type, FILE *fp)
+{
+  print_type_recurse (type, 4, fp);
+}
+#endif
+
+struct go_package
+{
+  hash_table /* const char[] -> go_type_t * */ defined_types;
+  hash_table /* const char[] -> go_type_t * */ globals;
+};
+
+static void
+add_to_hash_table (hash_table *htab, const char *name, go_type_t *type)
+{
+  if (hash_insert_entry (htab, name, strlen (name), type) == 0)
+    /* We have duplicates!  */
+    abort ();
+}
+
+static void
+add_method (go_type_t *recipient_type, const char *name, go_type_t *func_type)
+{
+  if (recipient_type->e == pointer)
+    /* Defining a method on *T is equivalent to defining a method on T.  */
+    add_method (recipient_type->u.eltype, name, func_type);
+  else if (recipient_type->e == go_struct)
+    {
+      unsigned int n = recipient_type->u.struct_def.n_methods;
+      if (n >= recipient_type->u.struct_def.n_methods_allocated)
+        {
+          unsigned int new_allocated =
+            2 * recipient_type->u.struct_def.n_methods_allocated + 1;
+          recipient_type->u.struct_def.methods =
+            (struct go_struct_method *)
+            xrealloc (recipient_type->u.struct_def.methods,
+                      new_allocated * sizeof (struct go_struct_method));
+          recipient_type->u.struct_def.n_methods_allocated = new_allocated;
+        }
+      recipient_type->u.struct_def.methods[n].name = name;
+      recipient_type->u.struct_def.methods[n].type = func_type;
+      recipient_type->u.struct_def.n_methods = n + 1;
+    }
+  else
+    abort ();
+}
+
+/* Full name of package github.com/leonelquinteros/gotext.  */
+#define GOTEXT_PACKAGE_FULLNAME "github.com/leonelquinteros/gotext"
+
+/* Known type information for the package github.com/leonelquinteros/gotext.  */
+static struct go_package gotext_package;
+
+/* Initializes gotext_package.  */
+static void
+init_gotext_package (void)
+{
+  /* Hand-extracted from
+     <https://pkg.go.dev/github.com/leonelquinteros/gotext@v1.7.0>  */
+
+  /* Initialize the hash tables.  */
+  hash_init (&gotext_package.defined_types, 10);
+  hash_init (&gotext_package.globals, 100);
+
+  /* Fill the gotext_package.defined_types table.  */
+
+  struct go_type *string_type = &a_predeclared_type;
+  struct go_type *func_returning_string = create_function_type (1, &string_type);
+
+  struct go_type *HeaderMap_type =
+    create_map_type (create_array_type (string_type));
+
+  struct go_type *Domain_type;
+  struct go_type *Mo_type;
+  struct go_type *Po_type;
+  {
+    struct go_struct_member members[3] =
+      {
+        { "Headers", HeaderMap_type },
+        { "Language", string_type },
+        { "PluralForms", string_type }
+      };
+    Domain_type = create_struct_type (3, members);
+    Mo_type = create_struct_type (3, members);
+    Po_type = create_struct_type (3, members);
+  }
+  add_to_hash_table (&gotext_package.defined_types, "Domain", Domain_type);
+  struct go_type *pDomain_type = create_pointer_type (Domain_type);
+  add_to_hash_table (&gotext_package.defined_types, "Mo", Mo_type);
+  struct go_type *pMo_type = create_pointer_type (Mo_type);
+  add_to_hash_table (&gotext_package.defined_types, "Po", Po_type);
+  struct go_type *pPo_type = create_pointer_type (Po_type);
+
+  struct go_type *Translator_type;
+  {
+    struct go_interface_member methods[9] =
+      {
+        { "ParseFile", &unknown_type },
+        { "Parse", &unknown_type },
+        { "Get", string_type },
+        { "GetC", string_type },
+        { "GetN", string_type },
+        { "GetNC", string_type },
+        { "MarshalBinary", &unknown_type },
+        { "UnmarshalBinary", &unknown_type },
+        { "GetDomain", pDomain_type }
+      };
+    Translator_type = create_interface_type (9, methods, 0, NULL);
+  }
+  add_to_hash_table (&gotext_package.defined_types, "Translator", Translator_type);
+
+  struct go_type *Translation_type;
+  {
+    struct go_struct_member members[4] =
+      {
+        { "ID", string_type },
+        { "PluralID", string_type },
+        { "Trs", create_map_type (string_type) },
+        { "Refs", create_array_type (string_type) }
+      };
+    Translation_type = create_struct_type (4, members);
+  }
+  add_to_hash_table (&gotext_package.defined_types, "Translation", Translation_type);
+  struct go_type *pTranslation_type = create_pointer_type (Translation_type);
+
+  struct go_type *Locale_type;
+  {
+    struct go_struct_member members[2] =
+      {
+        { "Domains", create_map_type (Translator_type) },
+        { "RWMutex", create_other_type ("sync.RWMutex") }
+      };
+    Locale_type = create_struct_type (2, members);
+  }
+  add_to_hash_table (&gotext_package.defined_types, "Locale", Locale_type);
+  struct go_type *pLocale_type = create_pointer_type (Locale_type);
+  struct go_type *apLocale_type = create_array_type (pLocale_type);
+
+  /* Fill the gotext_package.globals table and
+     insert methods on non-interface types.  */
+
+  add_to_hash_table (&gotext_package.globals, "Get", func_returning_string);
+  add_to_hash_table (&gotext_package.globals, "GetC", func_returning_string);
+  add_to_hash_table (&gotext_package.globals, "GetD", func_returning_string);
+  add_to_hash_table (&gotext_package.globals, "GetDC", func_returning_string);
+  add_to_hash_table (&gotext_package.globals, "GetN", func_returning_string);
+  add_to_hash_table (&gotext_package.globals, "GetNC", func_returning_string);
+  add_to_hash_table (&gotext_package.globals, "GetND", func_returning_string);
+  add_to_hash_table (&gotext_package.globals, "GetNDC", func_returning_string);
+
+  add_to_hash_table (&gotext_package.globals, "NewDomain",
+                     create_function_type (1, &pDomain_type));
+  add_method (pDomain_type, "Get", func_returning_string);
+  add_method (pDomain_type, "GetC", func_returning_string);
+  add_method (pDomain_type, "GetN", func_returning_string);
+  add_method (pDomain_type, "GetNC", func_returning_string);
+  add_method (pDomain_type, "GetTranslations",
+              create_map_type (pTranslation_type));
+
+  add_to_hash_table (&gotext_package.globals, "NewMo",
+                     create_function_type (1, &pMo_type));
+  add_to_hash_table (&gotext_package.globals, "NewMoFS",
+                     create_function_type (1, &pMo_type));
+  add_method (pMo_type, "Get", func_returning_string);
+  add_method (pMo_type, "GetC", func_returning_string);
+  add_method (pMo_type, "GetN", func_returning_string);
+  add_method (pMo_type, "GetNC", func_returning_string);
+  add_method (pMo_type, "GetDomain", pDomain_type);
+
+  add_to_hash_table (&gotext_package.globals, "NewPo",
+                     create_function_type (1, &pPo_type));
+  add_to_hash_table (&gotext_package.globals, "NewPoFS",
+                     create_function_type (1, &pPo_type));
+  add_method (pPo_type, "Get", func_returning_string);
+  add_method (pPo_type, "GetC", func_returning_string);
+  add_method (pPo_type, "GetN", func_returning_string);
+  add_method (pPo_type, "GetNC", func_returning_string);
+  add_method (pPo_type, "GetDomain", pDomain_type);
+
+  add_to_hash_table (&gotext_package.globals, "NewTranslation",
+                     create_function_type (1, &pTranslation_type));
+  add_to_hash_table (&gotext_package.globals, "NewTranslationWithRefs",
+                     create_function_type (1, &pTranslation_type));
+  add_method (pTranslation_type, "Get", func_returning_string);
+  add_method (pTranslation_type, "GetN", func_returning_string);
+
+  add_to_hash_table (&gotext_package.globals, "GetLocales",
+                     create_function_type (1, &apLocale_type));
+  add_to_hash_table (&gotext_package.globals, "NewLocale",
+                     create_function_type (1, &pLocale_type));
+  add_to_hash_table (&gotext_package.globals, "NewLocaleFS",
+                     create_function_type (1, &pLocale_type));
+  add_to_hash_table (&gotext_package.globals, "NewLocaleFSWithPath",
+                     create_function_type (1, &pLocale_type));
+  add_method (pLocale_type, "Get", func_returning_string);
+  add_method (pLocale_type, "GetC", func_returning_string);
+  add_method (pLocale_type, "GetD", func_returning_string);
+  add_method (pLocale_type, "GetDC", func_returning_string);
+  add_method (pLocale_type, "GetN", func_returning_string);
+  add_method (pLocale_type, "GetNC", func_returning_string);
+  add_method (pLocale_type, "GetND", func_returning_string);
+  add_method (pLocale_type, "GetNDC", func_returning_string);
+  add_method (pLocale_type, "GetDomain", func_returning_string);
+  add_method (pLocale_type, "GetTranslations",
+              create_map_type (pTranslation_type));
+}
+
+/* Full name of package github.com/snapcore/go-gettext.  */
+#define SNAPCORE_PACKAGE_FULLNAME "github.com/snapcore/go-gettext"
+/* Short name of that package.  */
+#define SNAPCORE_PACKAGE_SHORTNAME "gettext"
+
+/* Known type information for the package github.com/snapcore/go-gettext.  */
+static struct go_package snapcore_package;
+
+/* Initializes snapcore_package.  */
+static void
+init_snapcore_package (void)
+{
+  /* Hand-extracted from
+     <https://pkg.go.dev/github.com/snapcore/go-gettext>  */
+
+  /* Initialize the hash tables.  */
+  hash_init (&snapcore_package.defined_types, 10);
+  hash_init (&snapcore_package.globals, 100);
+
+  /* Fill the snapcore_package.defined_types table.  */
+
+  struct go_type *string_type = &a_predeclared_type;
+  struct go_type *func_returning_string = create_function_type (1, &string_type);
+
+  struct go_type *Catalog_type = create_struct_type (0, NULL);
+  add_to_hash_table (&snapcore_package.defined_types, "Catalog", Catalog_type);
+
+  struct go_type *TextDomain_type;
+  {
+    struct go_struct_member members[2] =
+      {
+        { "Name", string_type },
+        { "LocaleDir", string_type }
+      };
+    TextDomain_type = create_struct_type (2, members);
+  }
+  add_to_hash_table (&snapcore_package.defined_types, "TextDomain", TextDomain_type);
+  struct go_type *pTextDomain_type = create_pointer_type (TextDomain_type);
+
+  /* Fill the snapcore_package.globals table and
+     insert methods on non-interface types.  */
+
+  add_method (Catalog_type, "Gettext", func_returning_string);
+  add_method (Catalog_type, "NGettext", func_returning_string);
+  add_method (Catalog_type, "PGettext", func_returning_string);
+  add_method (Catalog_type, "NPGettext", func_returning_string);
+
+  add_method (pTextDomain_type, "Locale",
+              create_function_type (1, &Catalog_type));
+  add_method (pTextDomain_type, "UserLocale",
+              create_function_type (1, &Catalog_type));
+}
+
+
+/* ====================== Keyword set customization.  ====================== */
+
+/* If true extract all strings.  */
+static bool extract_all = false;
+
+/* For extracting calls like NAME (...).  */
+static hash_table /* string -> struct callshapes * */ keywords;
+/* For extracting calls like gotext.NAME (...).  */
+static hash_table /* string -> struct callshapes * */ gotext_keywords;
+/* For extracting calls like gettext.NAME (...).  */
+static hash_table /* string -> struct callshapes * */ snapcore_keywords;
+/* For extracting calls like gotext.TYPE.NAME (...).  */
+static gl_map_t /* go_type_t * -> hash_table (string -> struct callshapes *) * */ gotext_type_keywords;
+/* For extracting calls like gettext.TYPE.NAME (...).  */
+static gl_map_t /* go_type_t * -> hash_table (string -> struct callshapes *) * */ snapcore_type_keywords;
+static bool default_keywords = true;
+
+
+void
+x_go_extract_all ()
+{
+  extract_all = true;
+}
+
+
+void
+x_go_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);
+          hash_init (&gotext_keywords, 100);
+          hash_init (&snapcore_keywords, 100);
+          gotext_type_keywords = gl_map_create_empty (GL_HASH_MAP, NULL, NULL, NULL, NULL);
+          snapcore_type_keywords = gl_map_create_empty (GL_HASH_MAP, NULL, NULL, NULL, NULL);
+        }
+
+      split_keywordspec (name, &end, &shape);
+
+      /* A colon means an invalid parse in split_keywordspec().  */
+      colon = strchr (name, ':');
+      if (colon == NULL || colon >= end)
+        {
+          /* The characters between name and end should form
+               - either a valid Go identifier,
+               - or a PACKAGE . FUNCNAME,
+               - or a PACKAGE . TYPENAME . METHODNAME.  */
+          const char *last_slash = strrchr (name, '/');
+          if (last_slash != NULL && last_slash < end)
+            {
+              const char *first_dot = strchr (last_slash + 1, '.');
+              if (first_dot != NULL && first_dot < end)
+                {
+                  const char *second_dot = strchr (first_dot + 1, '.');
+                  if (second_dot != NULL && second_dot < end)
+                    {
+                      /* Looks like NAME is PACKAGE . TYPENAME . METHODNAME.  */
+                      /* We are only interested in the gotext and snapcore packages.  */
+                      if (first_dot - name == strlen (GOTEXT_PACKAGE_FULLNAME)
+                          && memcmp (name, GOTEXT_PACKAGE_FULLNAME, strlen (GOTEXT_PACKAGE_FULLNAME)) == 0)
+                        {
+                          void *found_type;
+                          if (hash_find_entry (&gotext_package.defined_types,
+                                               first_dot + 1, second_dot - (first_dot + 1),
+                                               &found_type)
+                              == 0)
+                            {
+                              go_type_t *gotext_type = (go_type_t *) found_type;
+                              hash_table *ht = (hash_table *) gl_map_get (gotext_type_keywords, gotext_type);
+                              if (ht == NULL)
+                                {
+                                  ht = XMALLOC (hash_table);
+                                  hash_init (ht, 100);
+                                  gl_map_put (gotext_type_keywords, gotext_type, ht);
+                                }
+                              insert_keyword_callshape (ht,
+                                                        second_dot + 1, end - (second_dot + 1),
+                                                        &shape);
+                            }
+                        }
+                      else if (first_dot - name == strlen (SNAPCORE_PACKAGE_FULLNAME)
+                               && memcmp (name, SNAPCORE_PACKAGE_FULLNAME, strlen (SNAPCORE_PACKAGE_FULLNAME)) == 0)
+                        {
+                          void *found_type;
+                          if (hash_find_entry (&snapcore_package.defined_types,
+                                               first_dot + 1, second_dot - (first_dot + 1),
+                                               &found_type)
+                              == 0)
+                            {
+                              go_type_t *snapcore_type = (go_type_t *) found_type;
+                              hash_table *ht = (hash_table *) gl_map_get (snapcore_type_keywords, snapcore_type);
+                              if (ht == NULL)
+                                {
+                                  ht = XMALLOC (hash_table);
+                                  hash_init (ht, 100);
+                                  gl_map_put (snapcore_type_keywords, snapcore_type, ht);
+                                }
+                              insert_keyword_callshape (ht,
+                                                        second_dot + 1, end - (second_dot + 1),
+                                                        &shape);
+                            }
+                        }
+                    }
+                  else
+                    {
+                      /* Looks like NAME is PACKAGE . FUNCNAME.  */
+                      /* We are only interested in the gotext and snapcore packages.  */
+                      if (first_dot - name == strlen (GOTEXT_PACKAGE_FULLNAME)
+                          && memcmp (name, GOTEXT_PACKAGE_FULLNAME, strlen (GOTEXT_PACKAGE_FULLNAME)) == 0)
+                        insert_keyword_callshape (&gotext_keywords,
+                                                  first_dot + 1, end - (first_dot + 1),
+                                                  &shape);
+                      else if (first_dot - name == strlen (SNAPCORE_PACKAGE_FULLNAME)
+                               && memcmp (name, SNAPCORE_PACKAGE_FULLNAME, strlen (SNAPCORE_PACKAGE_FULLNAME)) == 0)
+                        insert_keyword_callshape (&snapcore_keywords,
+                                                  first_dot + 1, end - (first_dot + 1),
+                                                  &shape);
+                    }
+                }
+            }
+          else
+            /* NAME looks like a valid Go identifier.  */
+            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)
+    {
+      /* These are the functions defined by the github.com/leonelquinteros/gotext
+         package.  */
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".Get:1");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".GetC:1,2c");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".GetD:2");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".GetDC:2,3c");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".GetN:1,2");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".GetNC:1,2,4c");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".GetND:2,3");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".GetNDC:2,3,5c");
+
+      /* These are the methods defined on types in the github.com/leonelquinteros/gotext
+         package.  */
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".Translator.Get:1");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".Translator.GetC:1,2c");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".Translator.GetN:1,2");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".Translator.GetNC:1,2,4c");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".Domain.Get:1");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".Domain.GetC:1,2c");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".Domain.GetN:1,2");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".Domain.GetNC:1,2,4c");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".Mo.Get:1");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".Mo.GetC:1,2c");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".Mo.GetN:1,2");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".Mo.GetNC:1,2,4c");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".Po.Get:1");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".Po.GetC:1,2c");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".Po.GetN:1,2");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".Po.GetNC:1,2,4c");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".Locale.Get:1");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".Locale.GetC:1,2c");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".Locale.GetD:2");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".Locale.GetDC:2,3c");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".Locale.GetN:1,2");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".Locale.GetNC:1,2,4c");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".Locale.GetND:2,3");
+      x_go_keyword (GOTEXT_PACKAGE_FULLNAME ".Locale.GetNDC:2,3,5c");
+
+      /* These are the methods defined on types in the github.com/snapcore/go-gettext
+         package.  */
+      x_go_keyword (SNAPCORE_PACKAGE_FULLNAME ".Catalog.Gettext:1");
+      x_go_keyword (SNAPCORE_PACKAGE_FULLNAME ".Catalog.NGettext:1,2");
+      x_go_keyword (SNAPCORE_PACKAGE_FULLNAME ".Catalog.PGettext:1c,2");
+      x_go_keyword (SNAPCORE_PACKAGE_FULLNAME ".Catalog.NPGettext:1c,2,3");
+
+      /* These are the functions defined by the github.com/gosexy/gettext package.  */
+      /* When adding new keywords here, also update the documentation in
+         xgettext.texi!  */
+      x_go_keyword ("Gettext:1");
+      x_go_keyword ("DGettext:2");
+      x_go_keyword ("DCGettext:2");
+      x_go_keyword ("NGettext:1,2");
+      x_go_keyword ("DNGettext:2,3");
+      x_go_keyword ("DCNGettext:2,3");
+
+      default_keywords = false;
+    }
+}
+
+void
+init_flag_table_go ()
+{
+  /* These are the functions and methods defined by the github.com/leonelquinteros/gotext
+     package.  */
+  xgettext_record_flag ("Get:1:pass-go-format");
+  xgettext_record_flag ("GetC:1:pass-go-format");
+  xgettext_record_flag ("GetD:2:pass-go-format");
+  xgettext_record_flag ("GetDC:2:pass-go-format");
+  xgettext_record_flag ("GetN:1:pass-go-format");
+  xgettext_record_flag ("GetN:2:pass-go-format");
+  xgettext_record_flag ("GetNC:1:pass-go-format");
+  xgettext_record_flag ("GetNC:2:pass-go-format");
+  xgettext_record_flag ("GetND:2:pass-go-format");
+  xgettext_record_flag ("GetND:3:pass-go-format");
+  xgettext_record_flag ("GetNDC:2:pass-go-format");
+  xgettext_record_flag ("GetNDC:3:pass-go-format");
+  /* These are the functions defined by the github.com/gosexy/gettext
+     and github.com/snapcore/go-gettext packages.  */
+  xgettext_record_flag ("Gettext:1:pass-go-format");
+  xgettext_record_flag ("DGettext:2:pass-go-format");
+  xgettext_record_flag ("DCGettext:2:pass-go-format");
+  xgettext_record_flag ("NGettext:1:pass-go-format");
+  xgettext_record_flag ("NGettext:2:pass-go-format");
+  xgettext_record_flag ("DNGettext:2:pass-go-format");
+  xgettext_record_flag ("DNGettext:3:pass-go-format");
+  xgettext_record_flag ("DCNGettext:2:pass-go-format");
+  xgettext_record_flag ("DCNGettext:3:pass-go-format");
+  xgettext_record_flag ("PGettext:2:pass-go-format");
+  xgettext_record_flag ("NPGettext:2:pass-go-format");
+  xgettext_record_flag ("NPGettext:3:pass-go-format");
+  /* These are the functions whose argument is a format string.
+     https://pkg.go.dev/fmt  */
+  xgettext_record_flag ("Sprintf:1:go-format");
+  xgettext_record_flag ("Fprintf:2:go-format");
+  xgettext_record_flag ("Printf:1:go-format");
+}
+
+
+/* ======================== Parsing via tree-sitter. ======================== */
+/* To understand this code, look at
+     tree-sitter-go/src/node-types.json
+   and
+     tree-sitter-go/src/grammar.json
+ */
+
+/* The tree-sitter's language object.  */
+static const TSLanguage *ts_language;
+
+/* ------------------------- Node types and symbols ------------------------- */
+
+static TSSymbol ts_language_symbol (const char *name, bool is_named)
+{
+  TSSymbol result =
+    ts_language_symbol_for_name (ts_language, name, strlen (name), is_named);
+  if (result == 0)
+    /* If we get here, the grammar has evolved in an incompatible way.  */
+    abort ();
+  return result;
+}
+
+static TSFieldId ts_language_field (const char *name)
+{
+  TSFieldId result =
+    ts_language_field_id_for_name (ts_language, name, strlen (name));
+  if (result == 0)
+    /* If we get here, the grammar has evolved in an incompatible way.  */
+    abort ();
+  return result;
+}
+
+/* Optimization:
+   Instead of
+     strcmp (ts_node_type (node), "interpreted_string_literal") == 0
+   it is faster to do
+     ts_node_symbol (node) == ts_symbol_interpreted_string_literal
+ */
+static TSSymbol ts_symbol_import_declaration;
+static TSSymbol ts_symbol_import_spec_list;
+static TSSymbol ts_symbol_import_spec;
+static TSSymbol ts_symbol_package_identifier;
+static TSSymbol ts_symbol_type_declaration;
+static TSSymbol ts_symbol_type_alias;
+static TSSymbol ts_symbol_type_spec;
+static TSSymbol ts_symbol_type_identifier;
+static TSSymbol ts_symbol_generic_type;
+static TSSymbol ts_symbol_qualified_type;
+static TSSymbol ts_symbol_pointer_type;
+static TSSymbol ts_symbol_struct_type;
+static TSSymbol ts_symbol_field_declaration_list;
+static TSSymbol ts_symbol_field_declaration;
+static TSSymbol ts_symbol_interface_type;
+static TSSymbol ts_symbol_method_elem;
+static TSSymbol ts_symbol_type_elem;
+static TSSymbol ts_symbol_array_type;
+static TSSymbol ts_symbol_slice_type;
+static TSSymbol ts_symbol_map_type;
+static TSSymbol ts_symbol_channel_type;
+static TSSymbol ts_symbol_function_type;
+static TSSymbol ts_symbol_parameter_list;
+static TSSymbol ts_symbol_parameter_declaration;
+static TSSymbol ts_symbol_variadic_parameter_declaration;
+static TSSymbol ts_symbol_negated_type;
+static TSSymbol ts_symbol_parenthesized_type;
+static TSSymbol ts_symbol_var_declaration;
+static TSSymbol ts_symbol_var_spec_list;
+static TSSymbol ts_symbol_var_spec;
+static TSSymbol ts_symbol_const_declaration;
+static TSSymbol ts_symbol_const_spec;
+static TSSymbol ts_symbol_short_var_declaration;
+static TSSymbol ts_symbol_expression_list;
+static TSSymbol ts_symbol_unary_expression;
+static TSSymbol ts_symbol_binary_expression;
+static TSSymbol ts_symbol_selector_expression;
+static TSSymbol ts_symbol_index_expression;
+static TSSymbol ts_symbol_slice_expression;
+static TSSymbol ts_symbol_call_expression;
+static TSSymbol ts_symbol_type_assertion_expression;
+static TSSymbol ts_symbol_type_conversion_expression;
+static TSSymbol ts_symbol_type_instantiation_expression;
+static TSSymbol ts_symbol_composite_literal;
+static TSSymbol ts_symbol_func_literal;
+static TSSymbol ts_symbol_int_literal;
+static TSSymbol ts_symbol_float_literal;
+static TSSymbol ts_symbol_imaginary_literal;
+static TSSymbol ts_symbol_rune_literal;
+static TSSymbol ts_symbol_nil;
+static TSSymbol ts_symbol_true;
+static TSSymbol ts_symbol_false;
+static TSSymbol ts_symbol_iota;
+static TSSymbol ts_symbol_parenthesized_expression;
+static TSSymbol ts_symbol_function_declaration;
+static TSSymbol ts_symbol_for_clause;
+static TSSymbol ts_symbol_comment;
+static TSSymbol ts_symbol_raw_string_literal;
+static TSSymbol ts_symbol_raw_string_literal_content;
+static TSSymbol ts_symbol_interpreted_string_literal;
+static TSSymbol ts_symbol_interpreted_string_literal_content;
+static TSSymbol ts_symbol_escape_sequence;
+static TSSymbol ts_symbol_argument_list;
+static TSSymbol ts_symbol_identifier;
+static TSSymbol ts_symbol_field_identifier;
+static TSSymbol ts_symbol_dot; /* . */
+static TSSymbol ts_symbol_plus; /* + */
+static TSFieldId ts_field_path;
+static TSFieldId ts_field_name;
+static TSFieldId ts_field_package;
+static TSFieldId ts_field_type;
+static TSFieldId ts_field_element;
+static TSFieldId ts_field_value;
+static TSFieldId ts_field_result;
+static TSFieldId ts_field_operator;
+static TSFieldId ts_field_left;
+static TSFieldId ts_field_right;
+static TSFieldId ts_field_function;
+static TSFieldId ts_field_arguments;
+static TSFieldId ts_field_operand;
+static TSFieldId ts_field_field;
+static TSFieldId ts_field_initializer;
+
+static inline size_t
+ts_node_line_number (TSNode node)
+{
+  return ts_node_start_point (node).row + 1;
+}
+
+/* -------------------------------- The file -------------------------------- */
+
+/* The entire contents of the file being analyzed.  */
+static const char *contents;
+
+/* ---------------------------- String literals ---------------------------- */
+
+/* Determines whether NODE represents a string literal or the concatenation
+   of string literals (via the '+' operator).  */
+static bool
+is_string_literal (TSNode node)
+{
+ start:
+  if (ts_node_symbol (node) == ts_symbol_raw_string_literal
+      || ts_node_symbol (node) == ts_symbol_interpreted_string_literal)
+    return true;
+  if (ts_node_symbol (node) == ts_symbol_binary_expression
+      && ts_node_symbol (ts_node_child_by_field_id (node, ts_field_operator)) == ts_symbol_plus
+      /* Recurse into the left and right subnodes.  */
+      && is_string_literal (ts_node_child_by_field_id (node, ts_field_right)))
+    {
+      /*return is_string_literal (ts_node_child_by_field_id (node, ts_field_left));*/
+      node = ts_node_child_by_field_id (node, ts_field_left);
+      goto start;
+    }
+  return false;
+}
+
+/* Prepends the string literal pieces from NODE to BUFFER.  */
+static void
+string_literal_accumulate_pieces (TSNode node,
+                                  struct string_buffer_reversed *buffer)
+{
+ start:
+  if (ts_node_symbol (node) == ts_symbol_raw_string_literal
+      || ts_node_symbol (node) == ts_symbol_interpreted_string_literal)
+    {
+      uint32_t count = ts_node_named_child_count (node);
+      uint32_t i;
+      for (i = count; i > 0; )
+        {
+          i--;
+          TSNode subnode = ts_node_named_child (node, i);
+          if (ts_node_symbol (subnode) == ts_symbol_raw_string_literal_content)
+            {
+              string_desc_t subnode_string =
+                sd_new_addr (ts_node_end_byte (subnode) - ts_node_start_byte (subnode),
+                             (char *) contents + ts_node_start_byte (subnode));
+              /* Eliminate '\r' characters.  */
+              for (;;)
+                {
+                  ptrdiff_t cr_index = sd_last_index (subnode_string, '\r');
+                  if (cr_index < 0)
+                    break;
+                  sbr_xprepend_desc (buffer,
+                                     sd_substring (subnode_string, cr_index + 1,
+                                                   sd_length (subnode_string)));
+                  subnode_string = sd_substring (subnode_string, 0, cr_index);
+                }
+              sbr_xprepend_desc (buffer, subnode_string);
+            }
+          else if (ts_node_symbol (subnode) == ts_symbol_interpreted_string_literal_content)
+            {
+              string_desc_t subnode_string =
+                sd_new_addr (ts_node_end_byte (subnode) - ts_node_start_byte (subnode),
+                             (char *) contents + ts_node_start_byte (subnode));
+              sbr_xprepend_desc (buffer, subnode_string);
+            }
+          else if (ts_node_symbol (subnode) == ts_symbol_escape_sequence)
+            {
+              const char *escape_start = contents + ts_node_start_byte (subnode);
+              const char *escape_end = contents + ts_node_end_byte (subnode);
+              /* The escape sequence must start with a backslash.  */
+              if (!(escape_end - escape_start >= 2 && escape_start[0] == '\\'))
+                abort ();
+              /* tree-sitter's grammar.js allows more escape sequences than
+                 the Go documentation and the Go compiler.  Give a warning
+                 for those case where the Go compiler gives an error.  */
+              bool invalid = false;
+              if (escape_end - escape_start == 2)
+                {
+                  switch (escape_start[1])
+                    {
+                    case '\\':
+                    case '"':
+                      sbr_xprepend1 (buffer, escape_start[1]);
+                      break;
+                    case 'a':
+                      sbr_xprepend1 (buffer, 0x07);
+                      break;
+                    case 'b':
+                      sbr_xprepend1 (buffer, 0x08);
+                      break;
+                    case 'f':
+                      sbr_xprepend1 (buffer, 0x0C);
+                      break;
+                    case 'n':
+                      sbr_xprepend1 (buffer, '\n');
+                      break;
+                    case 'r':
+                      sbr_xprepend1 (buffer, '\r');
+                      break;
+                    case 't':
+                      sbr_xprepend1 (buffer, '\t');
+                      break;
+                    case 'v':
+                      sbr_xprepend1 (buffer, 0x0B);
+                      break;
+                    default:
+                      invalid = true;
+                      break;
+                    }
+                }
+              else if (escape_start[1] >= '0' && escape_start[1] <= '9')
+                {
+                  unsigned int value = 0;
+                  /* Only exactly 3 octal digits are accepted.  */
+                  if (escape_end - escape_start == 1 + 3)
+                    {
+                      const char *p;
+                      for (p = escape_start + 1; p < escape_end; p++)
+                        {
+                          /* No overflow is possible.  */
+                          char c = *p;
+                          if (c >= '0' && c <= '7')
+                            value = (value << 3) + (c - '0');
+                          else
+                            invalid = true;
+                        }
+                      if (value > 0xFF)
+                        invalid = true;
+                    }
+                  if (!invalid)
+                    sbr_xprepend1 (buffer, (unsigned char) value);
+                }
+              else if ((escape_start[1] == 'x' && escape_end - escape_start == 2 + 2)
+                       || (escape_start[1] == 'u' && escape_end - escape_start == 2 + 4)
+                       || (escape_start[1] == 'U' && escape_end - escape_start == 2 + 8))
+                {
+                  unsigned int value = 0;
+                  const char *p;
+                  for (p = escape_start + 2; p < escape_end; p++)
+                    {
+                      /* No overflow is possible.  */
+                      char c = *p;
+                      if (c >= '0' && c <= '9')
+                        value = (value << 4) + (c - '0');
+                      else if (c >= 'A' && c <= 'Z')
+                        value = (value << 4) + (c - 'A' + 10);
+                      else if (c >= 'a' && c <= 'z')
+                        value = (value << 4) + (c - 'a' + 10);
+                      else
+                        invalid = true;
+                    }
+                  if (escape_start[1] == 'x')
+                    {
+                      if (!invalid)
+                        sbr_xprepend1 (buffer, (unsigned char) value);
+                    }
+                  else
+                    {
+                      if (value >= 0x110000 || (value >= 0xD800 && value <= 0xDFFF))
+                        invalid = true;
+                      if (!invalid)
+                        {
+                          uint8_t buf[6];
+                          int n = u8_uctomb (buf, value, sizeof (buf));
+                          if (n > 0)
+                            sbr_xprepend_desc (buffer, sd_new_addr (n, (char *) buf));
+                          else
+                            invalid = true;
+                        }
+                    }
+                }
+              else
+                invalid = true;
+              if (invalid)
+                {
+                  size_t line_number = ts_node_line_number (subnode);
+                  if_error (IF_SEVERITY_WARNING,
+                            logical_file_name, line_number, (size_t)(-1), false,
+                            _("invalid escape sequence in string"));
+                }
+            }
+          else
+            abort ();
+        }
+    }
+  else if (ts_node_symbol (node) == ts_symbol_binary_expression
+           && ts_node_symbol (ts_node_child_by_field_id (node, ts_field_operator)) == ts_symbol_plus)
+    {
+      /* Recurse into the left and right subnodes.  */
+      string_literal_accumulate_pieces (ts_node_child_by_field_id (node, ts_field_right), buffer);
+      /*string_literal_accumulate_pieces (ts_node_child_by_field_id (node, ts_field_left), buffer, contents);*/
+      node = ts_node_child_by_field_id (node, ts_field_left);
+      goto start;
+    }
+  else
+    abort ();
+}
+
+/* Combines the pieces of a raw_string_literal or interpreted_string_literal
+   or concatenated string literal.
+   Returns a freshly allocated, mostly UTF-8 encoded string.  */
+static char *
+string_literal_value (TSNode node)
+{
+  if (ts_node_symbol (node) == ts_symbol_interpreted_string_literal
+      && ts_node_named_child_count (node) == 1)
+    {
+      TSNode subnode = ts_node_named_child (node, 0);
+      if (ts_node_symbol (subnode) == ts_symbol_interpreted_string_literal_content)
+        {
+          /* Optimize the frequent special case of an interpreted string literal
+             that is non-empty and has no escape sequences.  */
+          string_desc_t subnode_string =
+            sd_new_addr (ts_node_end_byte (subnode) - ts_node_start_byte (subnode),
+                         (char *) contents + ts_node_start_byte (subnode));
+          return xsd_c (subnode_string);
+        }
+    }
+
+  /* The general case.  */
+  struct string_buffer_reversed buffer;
+  sbr_init (&buffer);
+  string_literal_accumulate_pieces (node, &buffer);
+  return sbr_xdupfree_c (&buffer);
+}
+
+/* ------------------- Imported packages and their names ------------------- */
+
+/* Table that maps a package_shortname to the full package name.  */
+static hash_table /* const char[] -> const char * */ package_table;
+
+/* List of packages whose entities must be accessed without a
+   package_shortname.  */
+static string_list_ty unqualified_packages;
+
+/* import_spec_node is of type import_spec.  */
+static void
+scan_import_spec (TSNode import_spec_node)
+{
+  TSNode path_node = ts_node_child_by_field_id (import_spec_node, ts_field_path);
+  if (!is_string_literal (path_node))
+    abort ();
+  char *path = string_literal_value (path_node);
+
+  TSNode name_node = ts_node_child_by_field_id (import_spec_node, ts_field_name);
+  string_desc_t shortname;
+  if (ts_node_is_null (name_node))
+    {
+      /* A package is imported without a name.
+         The package_shortname is the last element of the path, except in
+         special cases.  */
+      if (strcmp (path, SNAPCORE_PACKAGE_FULLNAME) == 0)
+        shortname = sd_from_c (SNAPCORE_PACKAGE_SHORTNAME);
+      else
+        {
+          const char *last_slash = strrchr (path, '/');
+          shortname = sd_from_c (last_slash != NULL ? last_slash + 1 : path);
+        }
+    }
+  else if (ts_node_symbol (name_node) == ts_symbol_package_identifier)
+    {
+      /* A package is imported with a name.  */
+      shortname =
+        sd_new_addr (ts_node_end_byte (name_node) - ts_node_start_byte (name_node),
+                     (char *) contents + ts_node_start_byte (name_node));
+    }
+  else
+    {
+      if (ts_node_symbol (name_node) == ts_symbol_dot)
+        {
+          /* A package is imported without a package_shortname.  */
+          string_list_append (&unqualified_packages, path);
+        }
+      return;
+    }
+  hash_set_value (&package_table,
+                  sd_data (shortname), sd_length (shortname),
+                  path);
+}
+
+/* node is of type import_declaration.  */
+static void
+scan_import_declaration (TSNode node)
+{
+  uint32_t count = ts_node_named_child_count (node);
+  uint32_t i;
+  for (i = 0; i < count; i++)
+    {
+      TSNode subnode = ts_node_named_child (node, i);
+      if (ts_node_symbol (subnode) == ts_symbol_import_spec_list)
+        {
+          uint32_t count2 = ts_node_named_child_count (subnode);
+          uint32_t j;
+          for (j = 0; j < count2; j++)
+            {
+              TSNode subsubnode = ts_node_named_child (subnode, j);
+              if (ts_node_symbol (subsubnode) == ts_symbol_import_spec)
+                scan_import_spec (subsubnode);
+            }
+        }
+      else if (ts_node_symbol (subnode) == ts_symbol_import_spec)
+        scan_import_spec (subnode);
+    }
+}
+
+/* Initializes current_package.  */
+static void
+init_package_table (TSNode root_node)
+{
+  /* Initialize the hash table.  */
+  hash_init (&package_table, 50);
+
+  /* Initialize the unqualified packages list.  */
+  string_list_init (&unqualified_packages);
+
+  /* Single pass through all top-level import declarations.  */
+  {
+    uint32_t count = ts_node_named_child_count (root_node);
+    uint32_t i;
+    for (i = 0; i < count; i++)
+      {
+        TSNode node = ts_node_named_child (root_node, i);
+        if (ts_node_symbol (node) == ts_symbol_import_declaration)
+          scan_import_declaration (node);
+      }
+  }
+}
+
+/* ----------------- Go type analysis: Tracking local types ----------------- */
+
+/* A type environment consists of type bindings, each with a nested scope.
+   The type environment valid outside of functions is represented by NULL.  */
+
+struct type_binding
+{
+  string_desc_t name;
+  go_type_t *type;
+};
+
+struct type_env
+{
+  struct type_env *outer_env;
+  struct type_binding a_binding;
+};
+typedef struct type_env *type_env_t;
+
+/* Augments a type_env_t.  */
+static type_env_t
+type_env_augment (type_env_t env, string_desc_t name, go_type_t *type)
+{
+  struct type_env *inner = XMALLOC (struct type_env);
+  inner->outer_env = env;
+  inner->a_binding.name = name;
+  inner->a_binding.type = type;
+  return inner;
+}
+
+/* --------------------- First pass of Go type analysis --------------------- */
+
+/* Known type information for the file being parsed.  */
+/* TODO: The type information should include the entire package, not only
+   a single file.  */
+static struct go_package current_package;
+
+/* Forward declaration.  */
+static go_type_t *get_type_from_type_node (TSNode type_node, type_env_t tenv, bool use_indirections);
+
+/* Returns the type definition of the given type, as a 'go_type_t *'.  */
+static go_type_t *
+get_type_from_type_name (string_desc_t type_name, type_env_t tenv, bool use_indirections)
+{
+  if (sd_equals (type_name, sd_from_c ("bool"))
+      || sd_equals (type_name, sd_from_c ("uint8"))
+      || sd_equals (type_name, sd_from_c ("uint16"))
+      || sd_equals (type_name, sd_from_c ("uint32"))
+      || sd_equals (type_name, sd_from_c ("uint64"))
+      || sd_equals (type_name, sd_from_c ("int8"))
+      || sd_equals (type_name, sd_from_c ("int16"))
+      || sd_equals (type_name, sd_from_c ("int32"))
+      || sd_equals (type_name, sd_from_c ("int64"))
+      || sd_equals (type_name, sd_from_c ("float32"))
+      || sd_equals (type_name, sd_from_c ("float64"))
+      || sd_equals (type_name, sd_from_c ("complex64"))
+      || sd_equals (type_name, sd_from_c ("complex128"))
+      || sd_equals (type_name, sd_from_c ("byte"))
+      || sd_equals (type_name, sd_from_c ("rune"))
+      || sd_equals (type_name, sd_from_c ("uint"))
+      || sd_equals (type_name, sd_from_c ("int"))
+      || sd_equals (type_name, sd_from_c ("uintptr"))
+      || sd_equals (type_name, sd_from_c ("string"))
+      || sd_equals (type_name, sd_from_c ("error"))
+      || sd_equals (type_name, sd_from_c ("comparable"))
+      || sd_equals (type_name, sd_from_c ("any")))
+    return &a_predeclared_type;
+  for (; tenv != NULL; tenv = tenv->outer_env)
+    {
+      if (sd_equals (type_name, tenv->a_binding.name))
+        return tenv->a_binding.type;
+    }
+  if (use_indirections)
+    {
+      /* We create an indirection because the type is not yet registered in
+         current_package.defined_types.  */
+      go_type_t *result = XMALLOC (struct go_type);
+      result->e = indirection;
+      result->u.type_name = xsd_c (type_name);
+      return result;
+    }
+  else
+    {
+      /* Look up the type.  */
+      void *found_type;
+      if (hash_find_entry (&current_package.defined_types,
+                           sd_data (type_name), sd_length (type_name),
+                           &found_type)
+          == 0)
+        return (go_type_t *) found_type;
+      {
+        size_t i;
+        for (i = 0; i < unqualified_packages.nitems; i++)
+          {
+            const char *unqualified_package = unqualified_packages.item[i];
+            if (strcmp (unqualified_package, GOTEXT_PACKAGE_FULLNAME) == 0)
+              {
+                if (hash_find_entry (&gotext_package.defined_types,
+                                     sd_data (type_name), sd_length (type_name),
+                                     &found_type)
+                    == 0)
+                  return (go_type_t *) found_type;
+              }
+            else if (strcmp (unqualified_package, SNAPCORE_PACKAGE_FULLNAME) == 0)
+              {
+                if (hash_find_entry (&snapcore_package.defined_types,
+                                     sd_data (type_name), sd_length (type_name),
+                                     &found_type)
+                    == 0)
+                  return (go_type_t *) found_type;
+              }
+          }
+      }
+      return &unknown_type;
+    }
+}
+
+/* type_node is of type type_identifier.  */
+static go_type_t *
+get_type_from_type_identifier_node (TSNode type_node, type_env_t tenv, bool use_indirections)
+{
+  string_desc_t type_name =
+    sd_new_addr (ts_node_end_byte (type_node) - ts_node_start_byte (type_node),
+                 (char *) contents + ts_node_start_byte (type_node));
+  return get_type_from_type_name (type_name, tenv, use_indirections);
+}
+
+/* type_node is of type function_type or method_elem or function_declaration or func_literal.  */
+static go_type_t *
+get_type_from_function_or_method_node (TSNode type_node, type_env_t tenv, bool use_indirections)
+{
+  TSNode result_node = ts_node_child_by_field_id (type_node, ts_field_result);
+  if (ts_node_is_null (result_node))
+    {
+      /* A function without return value.  */
+      struct go_type *value_type = &unknown_type;
+      return create_function_type (1, &value_type);
+    }
+  else if (ts_node_symbol (result_node) == ts_symbol_parameter_list)
+    {
+      /* A function with multiple return values.  */
+      uint32_t count = ts_node_named_child_count (result_node);
+      unsigned int n_values;
+      uint32_t i;
+      {
+        n_values = 0;
+        for (i = 0; i < count; i++)
+          {
+            TSNode subnode = ts_node_named_child (result_node, i);
+            if (ts_node_symbol (subnode) == ts_symbol_parameter_declaration)
+              n_values++;
+          }
+      }
+      struct go_type **values = XNMALLOC (n_values, struct go_type *);
+      {
+        unsigned int n = 0;
+        for (i = 0; i < count; i++)
+          {
+            TSNode subnode = ts_node_named_child (result_node, i);
+            if (ts_node_symbol (subnode) == ts_symbol_parameter_declaration)
+              {
+                values[n] =
+                  get_type_from_type_node (
+                    ts_node_child_by_field_id (subnode, ts_field_type),
+                    tenv, use_indirections);
+                n++;
+              }
+          }
+      }
+      struct go_type *function_type = create_function_type (n_values, values);
+      free (values);
+      return function_type;
+    }
+  else
+    {
+      /* A function with a single return value.  */
+      struct go_type *value_type =
+        get_type_from_type_node (result_node, tenv, use_indirections);
+      return create_function_type (1, &value_type);
+    }
+}
+
+static go_type_t *
+get_type_from_type_node (TSNode type_node, type_env_t tenv, bool use_indirections)
+{
+  #if DEBUG_GO && 0
+  string_desc_t type_node_name =
+    sd_new_addr (ts_node_end_byte (type_node) - ts_node_start_byte (type_node),
+                 (char *) contents + ts_node_start_byte (type_node));
+  fprintf (stderr, "type_node = [%s]|%s| = %s\n", ts_node_type (type_node), ts_node_string (type_node), sd_c (type_node_name));
+  #endif
+  while (ts_node_symbol (type_node) == ts_symbol_parenthesized_type
+         && ts_node_named_child_count (type_node) == 1)
+    {
+      type_node = ts_node_named_child (type_node, 0);
+    }
+
+  if (ts_node_symbol (type_node) == ts_symbol_type_identifier)
+    return get_type_from_type_identifier_node (type_node, tenv, use_indirections);
+  else if (ts_node_symbol (type_node) == ts_symbol_qualified_type)
+    {
+      /* A qualified type is of the form package_shortname.name.  */
+      TSNode shortname_node = ts_node_child_by_field_id (type_node, ts_field_package);
+      string_desc_t shortname =
+        sd_new_addr (ts_node_end_byte (shortname_node) - ts_node_start_byte (shortname_node),
+                     (char *) contents + ts_node_start_byte (shortname_node));
+      /* Look up the package's full name.  */
+      void *found_package;
+      if (hash_find_entry (&package_table,
+                           sd_data (shortname), sd_length (shortname),
+                           &found_package)
+          == 0)
+        {
+          if (strcmp ((const char *) found_package, GOTEXT_PACKAGE_FULLNAME) == 0)
+            {
+              /* Look up the type.  */
+              TSNode name_node = ts_node_child_by_field_id (type_node, ts_field_name);
+              string_desc_t name =
+                sd_new_addr (ts_node_end_byte (name_node) - ts_node_start_byte (name_node),
+                             (char *) contents + ts_node_start_byte (name_node));
+              void *found_type;
+              if (hash_find_entry (&gotext_package.defined_types,
+                                   sd_data (name), sd_length (name),
+                                   &found_type)
+                  == 0)
+                return (go_type_t *) found_type;
+            }
+          else if (strcmp ((const char *) found_package, SNAPCORE_PACKAGE_FULLNAME) == 0)
+            {
+              /* Look up the type.  */
+              TSNode name_node = ts_node_child_by_field_id (type_node, ts_field_name);
+              string_desc_t name =
+                sd_new_addr (ts_node_end_byte (name_node) - ts_node_start_byte (name_node),
+                             (char *) contents + ts_node_start_byte (name_node));
+              void *found_type;
+              if (hash_find_entry (&snapcore_package.defined_types,
+                                   sd_data (name), sd_length (name),
+                                   &found_type)
+                  == 0)
+                return (go_type_t *) found_type;
+            }
+        }
+      return &unknown_type;
+    }
+  else if (ts_node_symbol (type_node) == ts_symbol_generic_type)
+    /* Ignore the generic type's type arguments.  */
+    return get_type_from_type_node (
+             ts_node_child_by_field_id (type_node, ts_field_type),
+             tenv, use_indirections);
+  else if (ts_node_symbol (type_node) == ts_symbol_pointer_type)
+    {
+      TSNode eltype_node = ts_node_named_child (type_node, 0);
+      if (!ts_node_is_null (eltype_node))
+        return create_pointer_type (
+                 get_type_from_type_node (eltype_node, tenv, use_indirections));
+      return &unknown_type;
+    }
+  else if (ts_node_symbol (type_node) == ts_symbol_struct_type)
+    {
+      TSNode fdlnode = ts_node_named_child (type_node, 0);
+      if (!ts_node_is_null (fdlnode)
+          && ts_node_symbol (fdlnode) == ts_symbol_field_declaration_list)
+        {
+          uint32_t count = ts_node_named_child_count (fdlnode);
+          unsigned int n_members;
+          uint32_t i;
+          {
+            n_members = 0;
+            for (i = 0; i < count; i++)
+              {
+                TSNode fdnode = ts_node_named_child (fdlnode, i);
+                if (ts_node_symbol (fdnode) == ts_symbol_field_declaration)
+                  {
+                    uint32_t count2 = ts_node_named_child_count (fdnode);
+                    uint32_t j;
+                    for (j = 0; j < count2; j++)
+                      {
+                        TSNode subnode = ts_node_named_child (fdnode, j);
+                        if (ts_node_symbol (subnode) == ts_symbol_field_identifier)
+                          n_members++;
+                      }
+                    /* TODO: Handle embedded fields.  */
+                  }
+              }
+          }
+          struct go_struct_member *members = XNMALLOC (n_members, struct go_struct_member);
+          {
+            unsigned int n = 0;
+            for (i = 0; i < count; i++)
+              {
+                TSNode fdnode = ts_node_named_child (fdlnode, i);
+                if (ts_node_symbol (fdnode) == ts_symbol_field_declaration)
+                  {
+                    TSNode eltype_node = ts_node_child_by_field_id (fdnode, ts_field_type);
+                    go_type_t *eltype =
+                      get_type_from_type_node (eltype_node, tenv, use_indirections);
+                    uint32_t count2 = ts_node_named_child_count (fdnode);
+                    uint32_t j;
+                    for (j = 0; j < count2; j++)
+                      {
+                        TSNode subnode = ts_node_named_child (fdnode, j);
+                        if (ts_node_symbol (subnode) == ts_symbol_field_identifier)
+                          {
+                            members[n].name =
+                              xsd_c (
+                                sd_new_addr (ts_node_end_byte (subnode) - ts_node_start_byte (subnode),
+                                             (char *) contents + ts_node_start_byte (subnode)));
+                            members[n].type = eltype;
+                            n++;
+                          }
+                      }
+                    /* TODO: Handle embedded fields.  */
+                  }
+              }
+          }
+          struct go_type *struct_type = create_struct_type (n_members, members);
+          free (members);
+          return struct_type;
+        }
+      return &unknown_type;
+    }
+  else if (ts_node_symbol (type_node) == ts_symbol_interface_type)
+    {
+      uint32_t count = ts_node_named_child_count (type_node);
+      unsigned int n_methods;
+      unsigned int n_interfaces;
+      uint32_t i;
+      {
+        n_methods = 0;
+        n_interfaces = 0;
+        for (i = 0; i < count; i++)
+          {
+            TSNode subnode = ts_node_named_child (type_node, i);
+            if (ts_node_symbol (subnode) == ts_symbol_method_elem)
+              n_methods++;
+            else if (ts_node_symbol (subnode) == ts_symbol_type_elem)
+              n_interfaces++;
+            /* TODO: Support also subnodes of the form ~T or T₁|...|Tₙ  */
+          }
+      }
+      struct go_interface_member *methods = XNMALLOC (n_methods, struct go_interface_member);
+      struct go_type **interfaces = XNMALLOC (n_interfaces, struct go_type *);
+      {
+        unsigned int nm = 0;
+        unsigned int ni = 0;
+        for (i = 0; i < count; i++)
+          {
+            TSNode subnode = ts_node_named_child (type_node, i);
+            if (ts_node_symbol (subnode) == ts_symbol_method_elem)
+              {
+                TSNode name_node = ts_node_child_by_field_id (subnode, ts_field_name);
+                if (ts_node_symbol (name_node) != ts_symbol_field_identifier)
+                  abort ();
+                string_desc_t name =
+                  sd_new_addr (ts_node_end_byte (name_node) - ts_node_start_byte (name_node),
+                               (char *) contents + ts_node_start_byte (name_node));
+                methods[nm].name = xsd_c (name);
+                methods[nm].type = get_type_from_function_or_method_node (subnode, tenv, use_indirections);
+                nm++;
+              }
+            else if (ts_node_symbol (subnode) == ts_symbol_type_elem)
+              {
+                TSNode subsubnode = ts_node_named_child (subnode, 0);
+                if (!ts_node_is_null (subsubnode)
+                    && ts_node_symbol (subsubnode) == ts_symbol_type_identifier)
+                  interfaces[ni] = get_type_from_type_identifier_node (subsubnode, tenv, use_indirections);
+                else
+                  interfaces[ni] = &unknown_type;
+                ni++;
+              }
+          }
+      }
+      struct go_type *interface_type =
+        create_interface_type (n_methods, methods, n_interfaces, interfaces);
+      free (interfaces);
+      free (methods);
+      return interface_type;
+    }
+  else if (ts_node_symbol (type_node) == ts_symbol_array_type
+           || ts_node_symbol (type_node) == ts_symbol_slice_type)
+    {
+      TSNode eltype_node = ts_node_child_by_field_id (type_node, ts_field_element);
+      return create_array_type (
+               get_type_from_type_node (eltype_node, tenv, use_indirections));
+    }
+  else if (ts_node_symbol (type_node) == ts_symbol_map_type)
+    {
+      TSNode eltype_node = ts_node_child_by_field_id (type_node, ts_field_value);
+      return create_map_type (
+               get_type_from_type_node (eltype_node, tenv, use_indirections));
+    }
+  else if (ts_node_symbol (type_node) == ts_symbol_channel_type)
+    return &a_channel_type;
+  else if (ts_node_symbol (type_node) == ts_symbol_function_type)
+    return get_type_from_function_or_method_node (type_node, tenv, use_indirections);
+  else
+    return &unknown_type;
+}
+
+/* node is of type type_declaration.  */
+static void
+store_type_declaration (TSNode node)
+{
+  uint32_t count = ts_node_named_child_count (node);
+  uint32_t i;
+  for (i = 0; i < count; i++)
+    {
+      TSNode subnode = ts_node_named_child (node, i);
+      if (ts_node_symbol (subnode) == ts_symbol_type_alias
+          || ts_node_symbol (subnode) == ts_symbol_type_spec)
+        {
+          TSNode name_node = ts_node_child_by_field_id (subnode, ts_field_name);
+          if (ts_node_symbol (name_node) != ts_symbol_type_identifier)
+            abort ();
+          string_desc_t name =
+            sd_new_addr (ts_node_end_byte (name_node) - ts_node_start_byte (name_node),
+                         (char *) contents + ts_node_start_byte (name_node));
+          #if DEBUG_GO && 0
+          fprintf (stderr, "Type name = %s\n", sd_c (name));
+          #endif
+          TSNode type_node = ts_node_child_by_field_id (subnode, ts_field_type);
+          go_type_t *type = get_type_from_type_node (type_node, NULL, true);
+          /* Store the type definition in current_package.defined_types.  */
+          hash_set_value (&current_package.defined_types,
+                          sd_data (name), sd_length (name),
+                          type);
+        }
+    }
+}
+
+static void
+store_top_level_type_declarations (TSNode root_node)
+{
+  uint32_t count = ts_node_named_child_count (root_node);
+  uint32_t i;
+  for (i = 0; i < count; i++)
+    {
+      TSNode node = ts_node_named_child (root_node, i);
+      if (ts_node_symbol (node) == ts_symbol_type_declaration)
+        store_type_declaration (node);
+    }
+}
+
+/* Tests whether the type declaration is circular.
+   Example: type ( Alias1 = Alias2
+                   Alias2 = Alias1 )
+ */
+static bool
+is_circular_type_declaration (go_type_t *type, type_env_t tenv)
+{
+  if (type->e != indirection)
+    return false;
+
+  /* Use Robert W. Floyd's cycle detection algorithm.  */
+  go_type_t *turtle1 = type;
+  go_type_t *turtle2 = type;
+
+  for (;;)
+    {
+      if (turtle1->e != indirection)
+        return false;
+      turtle1 = get_type_from_type_name (sd_from_c (turtle1->u.type_name), tenv, false);
+
+      if (turtle2->e != indirection)
+        return false;
+      turtle2 = get_type_from_type_name (sd_from_c (turtle2->u.type_name), tenv, false);
+      if (turtle2->e != indirection)
+        return false;
+      turtle2 = get_type_from_type_name (sd_from_c (turtle2->u.type_name), tenv, false);
+
+      if (turtle1 == turtle2)
+        return true;
+    }
+}
+
+/* Replace circular type declarations with unknown.
+   This ensures that we don't run into endless loops later.  */
+static void
+eliminate_indirection_loops (void)
+{
+  void *iter = NULL;
+  for (;;)
+    {
+      const void *name;
+      size_t namelen;
+      void **data_p;
+      if (hash_iterate_modify (&current_package.defined_types, &iter,
+                               &name, &namelen, &data_p) < 0)
+        break;
+      go_type_t *type = *data_p;
+      if (is_circular_type_declaration (type, NULL))
+        {
+          #if DEBUG_GO
+          fprintf (stderr, "Type %.*s is circular definition!\n", (int) namelen, (const char *) name);
+          #endif
+          *data_p = &unknown_type;
+        }
+    }
+}
+
+/* Resolves indirections to named types.
+   This modifies the go_type_t graph destructively.  */
+static void
+resolve_indirections (go_type_t **type_p)
+{
+  go_type_t *type = *type_p;
+  switch (type->e)
+    {
+    case indirection:
+      {
+        go_type_t *rtype = type;
+        /* This loop terminates, because we have already eliminated circular
+           type declarations.  */
+        do
+          rtype = get_type_from_type_name (sd_from_c (rtype->u.type_name), NULL, false);
+        while (rtype->e == indirection);
+        *type_p = rtype;
+      }
+      break;
+    case pointer:
+    case array:
+    case map:
+      resolve_indirections (&type->u.eltype);
+      break;
+    case function:
+      {
+        unsigned int n = type->u.function_def.n_values;
+        unsigned int i;
+        for (i = 0; i < n; i++)
+          resolve_indirections (&type->u.function_def.values[i]);
+      }
+      break;
+    case go_struct:
+      {
+        unsigned int n = type->u.struct_def.n_members;
+        unsigned int i;
+        for (i = 0; i < n; i++)
+          resolve_indirections (&type->u.struct_def.members[i].type);
+      }
+      {
+        unsigned int n = type->u.struct_def.n_methods;
+        unsigned int i;
+        for (i = 0; i < n; i++)
+          resolve_indirections (&type->u.struct_def.methods[i].type);
+      }
+      break;
+    case go_interface:
+      {
+        unsigned int n = type->u.interface_def.n_methods;
+        unsigned int i;
+        for (i = 0; i < n; i++)
+          resolve_indirections (&type->u.interface_def.methods[i].type);
+      }
+      {
+        unsigned int n = type->u.interface_def.n_interfaces;
+        unsigned int i;
+        for (i = 0; i < n; i++)
+          resolve_indirections (&type->u.interface_def.interfaces[i]);
+      }
+      break;
+    default:
+      break;
+    }
+}
+
+static void
+resolve_all_indirections (void)
+{
+  void *iter = NULL;
+  for (;;)
+    {
+      const void *name;
+      size_t namelen;
+      void **data_p;
+      if (hash_iterate_modify (&current_package.defined_types, &iter,
+                               &name, &namelen, &data_p) < 0)
+        break;
+      go_type_t *type = *data_p;
+      #if DEBUG_GO && 0
+      fprintf (stderr, "Resolving %.*s (e=%u)", (int) namelen, (const char *) name, type->e);
+      #endif
+      resolve_indirections (&type);
+      #if DEBUG_GO && 0
+      fprintf (stderr, " -> (e=%u)\n", type->e);
+      #endif
+      *data_p = type;
+    }
+}
+
+static void
+verify_no_more_indirections (void)
+{
+  void *iter = NULL;
+  for (;;)
+    {
+      const void *name;
+      size_t namelen;
+      void *data;
+      if (hash_iterate (&current_package.defined_types, &iter,
+                        &name, &namelen, &data) < 0)
+        break;
+      go_type_t *type = data;
+      #if DEBUG_GO
+      fprintf (stderr, "verify: %.*s -> ", (int) namelen, (const char *) name);
+      print_type (type, stderr);
+      fprintf (stderr, "\n");
+      #endif
+      if (type->e == indirection)
+        abort ();
+    }
+}
+
+/* Initializes current_package.defined_types.  */
+static void
+init_current_package_types (TSNode root_node)
+{
+  /* Initialize the hash table.  */
+  hash_init (&current_package.defined_types, 50);
+
+  /* Fill the current_package.defined_types table:
+     1. Single pass through all top-level type declarations.
+     2. Eliminate indirection loops.
+     3. Resolve indirections among the types found.  */
+  store_top_level_type_declarations (root_node);
+  eliminate_indirection_loops ();
+  resolve_all_indirections ();
+  verify_no_more_indirections ();
+}
+
+/* --------------- Go type analysis: Tracking local variables --------------- */
+
+/* A variable environment consists of variable bindings (of which we keep track
+   only of the type, not of the value), each with a nested scope.
+   The variable environment valid outside of functions is represented by
+   NULL.  */
+
+struct variable_binding
+{
+  string_desc_t name;
+  go_type_t *type;
+};
+
+struct variable_env
+{
+  struct variable_env *outer_env;
+  struct variable_binding a_binding;
+};
+typedef struct variable_env *variable_env_t;
+
+/* Augments a variable_env_t.  */
+static variable_env_t
+variable_env_augment (variable_env_t env, string_desc_t name, go_type_t *type)
+{
+  struct variable_env *inner = XMALLOC (struct variable_env);
+  inner->outer_env = env;
+  inner->a_binding.name = name;
+  inner->a_binding.type = type;
+  return inner;
+}
+
+/* Looks up the type corresponding to a variable name in a variable
+   environment.  */
+static go_type_t *
+variable_env_lookup (string_desc_t var_name, variable_env_t venv)
+{
+  for (; venv != NULL; venv = venv->outer_env)
+    {
+      if (sd_equals (var_name, venv->a_binding.name))
+        return venv->a_binding.type;
+    }
+  /* Lookup in the global variable environment.  */
+  {
+    void *found_type;
+    if (hash_find_entry (&current_package.globals,
+                         sd_data (var_name), sd_length (var_name),
+                         &found_type)
+        == 0)
+      return (go_type_t *) found_type;
+    {
+      size_t i;
+      for (i = 0; i < unqualified_packages.nitems; i++)
+        {
+          const char *unqualified_package = unqualified_packages.item[i];
+          if (strcmp (unqualified_package, GOTEXT_PACKAGE_FULLNAME) == 0)
+            {
+              if (hash_find_entry (&gotext_package.globals,
+                                   sd_data (var_name), sd_length (var_name),
+                                   &found_type)
+                  == 0)
+                return (go_type_t *) found_type;
+            }
+          else if (strcmp (unqualified_package, SNAPCORE_PACKAGE_FULLNAME) == 0)
+            {
+              if (hash_find_entry (&snapcore_package.globals,
+                                   sd_data (var_name), sd_length (var_name),
+                                   &found_type)
+                  == 0)
+                return (go_type_t *) found_type;
+            }
+        }
+    }
+    return &unknown_type;
+  }
+}
+
+/* ---------------- Go type analysis: Analyzing expressions ---------------- */
+
+/* The type that contains only the nil pointer.  */
+static go_type_t nil_type = { other, { NULL } };
+
+/* Determines whether two types are equivalent for our purposes.  */
+MAYBE_UNUSED static bool
+type_equals (go_type_t *type1, go_type_t *type2, unsigned int maxdepth)
+{
+  if (maxdepth == 0)
+    /* Recursion limit reached.  */
+    return false;
+  if (type1 == type2)
+    return true;
+  if (type1->e == type2->e)
+    {
+      maxdepth--;
+      switch (type1->e)
+        {
+        case pointer:
+        case array:
+        case map:
+          return type_equals (type1->u.eltype, type2->u.eltype, maxdepth);
+        case function:
+          if (type1->u.function_def.n_values == type2->u.function_def.n_values)
+            {
+              unsigned int n = type1->u.function_def.n_values;
+              unsigned int i;
+              for (i = 0; i < n; i++)
+                if (!type_equals (type1->u.function_def.values[i],
+                                  type2->u.function_def.values[i],
+                                  maxdepth))
+                  return false;
+              return true;
+            }
+          return false;
+        case go_struct:
+          if (type1->u.struct_def.n_members == type2->u.struct_def.n_members
+              && type1->u.struct_def.n_methods == type2->u.struct_def.n_methods)
+            {
+              {
+                unsigned int n = type1->u.struct_def.n_members;
+                unsigned int i;
+                for (i = 0; i < n; i++)
+                  if (strcmp (type1->u.struct_def.members[i].name,
+                              type2->u.struct_def.members[i].name) != 0)
+                    return false;
+                for (i = 0; i < n; i++)
+                  if (!type_equals (type1->u.struct_def.members[i].type,
+                                    type2->u.struct_def.members[i].type,
+                                    maxdepth))
+                    return false;
+              }
+              {
+                unsigned int n = type1->u.struct_def.n_methods;
+                unsigned int i;
+                for (i = 0; i < n; i++)
+                  if (strcmp (type1->u.struct_def.methods[i].name,
+                              type2->u.struct_def.methods[i].name) != 0)
+                    return false;
+                for (i = 0; i < n; i++)
+                  if (!type_equals (type1->u.struct_def.methods[i].type,
+                                    type2->u.struct_def.methods[i].type,
+                                    maxdepth))
+                    return false;
+              }
+              return true;
+            }
+          return false;
+        case go_interface:
+          if (type1->u.interface_def.n_methods == type2->u.interface_def.n_methods
+              && type1->u.interface_def.n_interfaces == type2->u.interface_def.n_interfaces)
+            {
+              {
+                unsigned int n = type1->u.interface_def.n_methods;
+                unsigned int i;
+                for (i = 0; i < n; i++)
+                  if (strcmp (type1->u.interface_def.methods[i].name,
+                              type2->u.interface_def.methods[i].name) != 0)
+                    return false;
+                for (i = 0; i < n; i++)
+                  if (!type_equals (type1->u.interface_def.methods[i].type,
+                                    type2->u.interface_def.methods[i].type,
+                                    maxdepth))
+                    return false;
+              }
+              {
+                unsigned int n = type1->u.interface_def.n_interfaces;
+                unsigned int i;
+                for (i = 0; i < n; i++)
+                  if (!type_equals (type1->u.interface_def.interfaces[i],
+                                    type2->u.interface_def.interfaces[i],
+                                    maxdepth))
+                    return false;
+              }
+              return true;
+            }
+          return false;
+        default:
+          return false;
+        }
+    }
+  return false;
+}
+
+/* Returns the union of TYPE1 and TYPE2.  */
+MAYBE_UNUSED static go_type_t *
+type_union (go_type_t *type1, go_type_t *type2)
+{
+  if (type1 == type2)
+    return type1;
+  if (type2 == &nil_type && type1->e == pointer)
+    return type1;
+  if (type1 == &nil_type && type2->e == pointer)
+    return type2;
+  if (type_equals (type1, type2, 100))
+    return type1;
+  return &unknown_type;
+}
+
+/* Forward declaration.  */
+static unsigned int
+get_mvtypes_of_expression (unsigned int mvcount, go_type_t **result,
+                           TSNode node, type_env_t tenv, variable_env_t venv);
+
+/* Returns the type of an expression, assuming a single-value context,
+   as far as it can be determined through a simple type analysis.  */
+static go_type_t *
+get_type_of_expression (TSNode node, type_env_t tenv, variable_env_t venv)
+{
+  go_type_t *result;
+  unsigned int count = get_mvtypes_of_expression (1, &result, node, tenv, venv);
+  if (count == 1)
+    return result;
+  else
+    return &unknown_type;
+}
+
+/* Returns the type of an expression, assuming a context with mvcount values,
+   as far as it can be determined through a simple type analysis.
+   mvcount must be >= 1.  There is room for result[0..mvcount-1].
+   The return value is the number of values found: >= 1, <= mvcount.  */
+static unsigned int
+get_mvtypes_of_expression (unsigned int mvcount, go_type_t **result,
+                           TSNode node, type_env_t tenv, variable_env_t venv)
+{
+#define return1(t) \
+  do { *result = (t); return 1; } while (0)
+
+  while (ts_node_symbol (node) == ts_symbol_parenthesized_expression
+         && ts_node_named_child_count (node) == 1)
+    {
+      node = ts_node_named_child (node, 0);
+    }
+
+  if (ts_node_symbol (node) == ts_symbol_expression_list)
+    {
+      if (ts_node_named_child_count (node) == mvcount)
+        {
+          /* Each of the mvcount expressions in node is expected to produce
+             a single value.  */
+          unsigned int i;
+          for (i = 0; i < mvcount; i++)
+            result[i] = get_type_of_expression (ts_node_named_child (node, i), tenv, venv);
+          return mvcount;
+        }
+      else if (ts_node_named_child_count (node) == 1)
+        {
+          /* node is an expression that is expected to produce mvcount values.  */
+          TSNode sub_expr = ts_node_named_child (node, 0);
+          unsigned int sub_mvcount =
+            get_mvtypes_of_expression (mvcount, result, sub_expr, tenv, venv);
+          if (sub_mvcount == mvcount)
+            return mvcount;
+        }
+      return1 (&unknown_type);
+    }
+  if (ts_node_symbol (node) == ts_symbol_identifier)
+    {
+      string_desc_t name =
+        sd_new_addr (ts_node_end_byte (node) - ts_node_start_byte (node),
+                     (char *) contents + ts_node_start_byte (node));
+      return1 (variable_env_lookup (name, venv));
+    }
+  if (ts_node_symbol (node) == ts_symbol_unary_expression)
+    {
+      TSNode operator_node = ts_node_child_by_field_id (node, ts_field_operator);
+      string_desc_t operator =
+        sd_new_addr (ts_node_end_byte (operator_node) - ts_node_start_byte (operator_node),
+                     (char *) contents + ts_node_start_byte (operator_node));
+      if (sd_equals (operator, sd_from_c ("*")))
+        {
+          TSNode operand_node = ts_node_child_by_field_id (node, ts_field_operand);
+          go_type_t *operand_type = get_type_of_expression (operand_node, tenv, venv);
+          if (operand_type->e == pointer)
+            return1 (operand_type->u.eltype);
+          else
+            return1 (&unknown_type);
+        }
+      if (sd_equals (operator, sd_from_c ("&")))
+        {
+          TSNode operand_node = ts_node_child_by_field_id (node, ts_field_operand);
+          go_type_t *operand_type = get_type_of_expression (operand_node, tenv, venv);
+          return1 (create_pointer_type (operand_type));
+        }
+      if (sd_equals (operator, sd_from_c ("<-")))
+        return1 (&unknown_type);
+      /* All other unary operators work on arithmetic types and strings.  */
+      return1 (&a_predeclared_type);
+    }
+  if (ts_node_symbol (node) == ts_symbol_binary_expression)
+    {
+      /* All binary operators work on arithmetic types and strings.  */
+      return1 (&a_predeclared_type);
+    }
+  if (ts_node_symbol (node) == ts_symbol_selector_expression)
+    {
+      TSNode field_node = ts_node_child_by_field_id (node, ts_field_field);
+      if (ts_node_symbol (field_node) != ts_symbol_field_identifier)
+        abort ();
+      string_desc_t field_name =
+        sd_new_addr (ts_node_end_byte (field_node) - ts_node_start_byte (field_node),
+                     (char *) contents + ts_node_start_byte (field_node));
+      TSNode operand_node = ts_node_child_by_field_id (node, ts_field_operand);
+      /* If the operand is a package name, we have in fact a qualified identifier.  */
+      if (ts_node_symbol (operand_node) == ts_symbol_identifier)
+        {
+          string_desc_t shortname =
+            sd_new_addr (ts_node_end_byte (operand_node) - ts_node_start_byte (operand_node),
+                         (char *) contents + ts_node_start_byte (operand_node));
+          /* Look up the package's full name.  */
+          void *found_package;
+          if (hash_find_entry (&package_table,
+                               sd_data (shortname), sd_length (shortname),
+                               &found_package)
+              == 0)
+            {
+              /* The operand is a package name.  */
+              if (strcmp ((const char *) found_package, GOTEXT_PACKAGE_FULLNAME) == 0)
+                {
+                  /* Look up the entity in the package.  */
+                  void *found_type;
+                  if (hash_find_entry (&gotext_package.globals,
+                                       sd_data (field_name), sd_length (field_name),
+                                       &found_type)
+                      == 0)
+                    return1 ((go_type_t *) found_type);
+                }
+              else if (strcmp ((const char *) found_package, SNAPCORE_PACKAGE_FULLNAME) == 0)
+                {
+                  /* Look up the entity in the package.  */
+                  void *found_type;
+                  if (hash_find_entry (&snapcore_package.globals,
+                                       sd_data (field_name), sd_length (field_name),
+                                       &found_type)
+                      == 0)
+                    return1 ((go_type_t *) found_type);
+                }
+              return1 (&unknown_type);
+            }
+        }
+      go_type_t *operand_type = get_type_of_expression (operand_node, tenv, venv);
+      if (operand_type->e == pointer)
+        operand_type = operand_type->u.eltype;
+      if (operand_type->e == go_struct)
+        {
+          unsigned int i;
+          for (i = 0; i < operand_type->u.struct_def.n_members; i++)
+            if (sd_equals (field_name, sd_from_c (operand_type->u.struct_def.members[i].name)))
+              return1 (operand_type->u.struct_def.members[i].type);
+          for (i = 0; i < operand_type->u.struct_def.n_methods; i++)
+            if (sd_equals (field_name, sd_from_c (operand_type->u.struct_def.methods[i].name)))
+              return1 (operand_type->u.struct_def.methods[i].type);
+          /* TODO: Handle embedded fields.  */
+        }
+      else if (operand_type->e == go_interface)
+        {
+          /* Find a method of the given name through a breadth-first search.  */
+          gl_list_t queued_interfaces =
+            gl_list_create_empty (GL_CARRAY_LIST, NULL, NULL, NULL, false);
+          gl_set_t visited_interfaces =
+            gl_set_create_empty (GL_HASH_SET, NULL, NULL, NULL);
+          gl_list_add_last (queued_interfaces, operand_type);
+          while (gl_list_size (queued_interfaces) > 0)
+            {
+              go_type_t *itf = (go_type_t *) gl_list_get_first (queued_interfaces);
+              gl_list_remove_first (queued_interfaces);
+              if (itf->e == go_interface
+                  && !gl_set_search (visited_interfaces, itf))
+                {
+                  gl_set_add (visited_interfaces, itf);
+                  /* Search among the methods directly defined in itf.  */
+                  unsigned int i;
+                  for (i = 0; i < itf->u.interface_def.n_methods; i++)
+                    if (sd_equals (field_name, sd_from_c (itf->u.interface_def.methods[i].name)))
+                      {
+                        gl_set_free (visited_interfaces);
+                        gl_list_free (queued_interfaces);
+                        return1 (itf->u.interface_def.methods[i].type);
+                      }
+                  /* Enqueue the embedded interfaces of itf.  */
+                  for (i = 0; i < itf->u.interface_def.n_interfaces; i++)
+                    gl_list_add_last (queued_interfaces, itf->u.interface_def.interfaces[i]);
+                }
+            }
+          gl_set_free (visited_interfaces);
+          gl_list_free (queued_interfaces);
+        }
+      return1 (&unknown_type);
+    }
+  if (ts_node_symbol (node) == ts_symbol_index_expression)
+    {
+      TSNode operand_node = ts_node_child_by_field_id (node, ts_field_operand);
+      go_type_t *operand_type = get_type_of_expression (operand_node, tenv, venv);
+      if (operand_type->e == array || operand_type->e == map)
+        return1 (operand_type->u.eltype);
+      if (operand_type->e == pointer && operand_type->u.eltype->e == array)
+        return1 (operand_type->u.eltype->u.eltype);
+      if (operand_type->e == predeclared)
+        /* Must be a string type.  */
+        return1 (&a_predeclared_type);
+      /* A generic function instantiation is returned as a node of type
+         index_expression.
+         Example: sum[int], cf. <https://go.dev/ref/spec#Instantiations>  */
+      if (operand_type->e == function)
+        /* We don't distinguish between generic and non-generic functions here.  */
+        return1 (operand_type);
+      return1 (&unknown_type);
+    }
+  if (ts_node_symbol (node) == ts_symbol_slice_expression)
+    {
+      TSNode operand_node = ts_node_child_by_field_id (node, ts_field_operand);
+      go_type_t *operand_type = get_type_of_expression (operand_node, tenv, venv);
+      if (operand_type->e == array)
+        return1 (operand_type);
+      if (operand_type->e == pointer && operand_type->u.eltype->e == array)
+        return1 (operand_type->u.eltype);
+      if (operand_type->e == predeclared)
+        /* Must be a string or bytestring type.  */
+        return1 (&a_predeclared_type);
+      return1 (&unknown_type);
+    }
+  if (ts_node_symbol (node) == ts_symbol_call_expression)
+    {
+      TSNode function_node = ts_node_child_by_field_id (node, ts_field_function);
+      // 'new' and 'make' are special.
+      if (ts_node_symbol (function_node) == ts_symbol_identifier)
+        {
+          string_desc_t function_name =
+            sd_new_addr (ts_node_end_byte (function_node) - ts_node_start_byte (function_node),
+                         (char *) contents + ts_node_start_byte (function_node));
+          if (sd_equals (function_name, sd_from_c ("new")))
+            {
+              TSNode args_node = ts_node_child_by_field_id (node, ts_field_arguments);
+              /* This is the field called 'arguments'.
+                 Recognize the syntax 'new (TYPE)'.  */
+              if (ts_node_symbol (args_node) == ts_symbol_argument_list
+                  && ts_node_named_child_count (args_node) == 1)
+                {
+                  TSNode type_node = ts_node_named_child (args_node, 0);
+                  go_type_t *type = get_type_from_type_node (type_node, tenv, false);
+                  return1 (create_pointer_type (type));
+                }
+              return1 (&unknown_type);
+            }
+          if (sd_equals (function_name, sd_from_c ("make")))
+            {
+              TSNode args_node = ts_node_child_by_field_id (node, ts_field_arguments);
+              /* This is the field called 'arguments'.
+                 Recognize the syntax 'make (TYPE, ...)'.  */
+              if (ts_node_symbol (args_node) == ts_symbol_argument_list
+                  && ts_node_named_child_count (args_node) >= 1)
+                {
+                  TSNode type_node = ts_node_named_child (args_node, 0);
+                  go_type_t *type = get_type_from_type_node (type_node, tenv, false);
+                  if (type->e == array || type->e == map || type->e == channel)
+                    return1 (type);
+                }
+              return1 (&unknown_type);
+            }
+        }
+      go_type_t *function_type = get_type_of_expression (function_node, tenv, venv);
+      if (function_type->e == function
+          && function_type->u.function_def.n_values == mvcount)
+        {
+          unsigned int i;
+          for (i = 0; i < mvcount; i++)
+            result[i] = function_type->u.function_def.values[i];
+          return mvcount;
+        }
+      return1 (&unknown_type);
+    }
+  if (ts_node_symbol (node) == ts_symbol_type_assertion_expression
+      || ts_node_symbol (node) == ts_symbol_type_conversion_expression
+      || ts_node_symbol (node) == ts_symbol_type_instantiation_expression)
+    {
+      TSNode type_node = ts_node_child_by_field_id (node, ts_field_type);
+      return1 (get_type_from_type_node (type_node, tenv, false));
+    }
+  if (ts_node_symbol (node) == ts_symbol_composite_literal)
+    {
+      TSNode type_node = ts_node_child_by_field_id (node, ts_field_type);
+      return1 (get_type_from_type_node (type_node, tenv, false));
+    }
+  if (ts_node_symbol (node) == ts_symbol_func_literal)
+    return1 (get_type_from_function_or_method_node (node, tenv, false));
+  if ((ts_node_symbol (node) == ts_symbol_raw_string_literal
+       || ts_node_symbol (node) == ts_symbol_interpreted_string_literal)
+      || ts_node_symbol (node) == ts_symbol_int_literal
+      || ts_node_symbol (node) == ts_symbol_float_literal
+      || ts_node_symbol (node) == ts_symbol_imaginary_literal
+      || ts_node_symbol (node) == ts_symbol_rune_literal
+      || ts_node_symbol (node) == ts_symbol_true
+      || ts_node_symbol (node) == ts_symbol_false
+      || ts_node_symbol (node) == ts_symbol_iota)
+    return1 (&a_predeclared_type);
+  if (ts_node_symbol (node) == ts_symbol_nil)
+    return1 (&nil_type);
+  return1 (&unknown_type);
+
+#undef return1
+}
+
+/* --------------- Go global variables and functions analysis --------------- */
+
+/* node is of type var_spec.  */
+static void
+store_var_spec (TSNode node)
+{
+  /* It may contain multiple names.  */
+  TSNode type_node = ts_node_child_by_field_id (node, ts_field_type);
+  if (!ts_node_is_null (type_node))
+    {
+      /* "If a type is present, each variable is given that type."  */
+      go_type_t *type = get_type_from_type_node (type_node, NULL, false);
+      uint32_t count = ts_node_named_child_count (node);
+      uint32_t i;
+      for (i = 0; i < count; i++)
+        {
+          TSNode subnode = ts_node_named_child (node, i);
+          if (ts_node_symbol (subnode) == ts_symbol_identifier)
+            {
+              TSNode name_node = subnode;
+              string_desc_t name =
+                sd_new_addr (ts_node_end_byte (name_node) - ts_node_start_byte (name_node),
+                             (char *) contents + ts_node_start_byte (name_node));
+              #if DEBUG_GO && 0
+              fprintf (stderr, "Var name = %s\n", sd_c (name));
+              #endif
+              /* Store the variable definition in current_package.globals.  */
+              hash_set_value (&current_package.globals,
+                              sd_data (name), sd_length (name),
+                              type);
+            }
+        }
+    }
+  else
+    {
+      /* "Otherwise, each variable is given the type of the corresponding
+          initialization value in the assignment."  */
+      uint32_t count = ts_node_named_child_count (node);
+      unsigned int mvcount;
+
+      mvcount = 0;
+      {
+        uint32_t i;
+        for (i = 0; i < count; i++)
+          {
+            TSNode subnode = ts_node_named_child (node, i);
+            if (ts_node_symbol (subnode) == ts_symbol_identifier)
+              mvcount++;
+          }
+      }
+      if (mvcount > 0)
+        {
+          /* We are in a context where mvcount values are expected.  */
+          TSNode value_node = ts_node_child_by_field_id (node, ts_field_value);
+          go_type_t **value_types = XNMALLOC (mvcount, go_type_t *);
+          unsigned int value_mvcount =
+            get_mvtypes_of_expression (mvcount, value_types, value_node, NULL, NULL);
+          if (value_mvcount != mvcount)
+            {
+              unsigned int j;
+              for (j = 0; j < mvcount; j++)
+                value_types[j] = &unknown_type;
+            }
+          unsigned int j = 0;
+          uint32_t i;
+          for (i = 0; i < count; i++)
+            {
+              TSNode subnode = ts_node_named_child (node, i);
+              if (ts_node_symbol (subnode) == ts_symbol_identifier)
+                {
+                  TSNode name_node = subnode;
+                  string_desc_t name =
+                    sd_new_addr (ts_node_end_byte (name_node) - ts_node_start_byte (name_node),
+                                 (char *) contents + ts_node_start_byte (name_node));
+                  #if DEBUG_GO && 0
+                  fprintf (stderr, "Var name = %s\n", sd_c (name));
+                  #endif
+                  /* Store the variable definition in current_package.globals.  */
+                  hash_set_value (&current_package.globals,
+                                  sd_data (name), sd_length (name),
+                                  value_types[j]);
+                  j++;
+                }
+            }
+          free (value_types);
+        }
+    }
+}
+
+/* node is of type var_spec_list.  */
+static void
+store_var_spec_list (TSNode node)
+{
+  uint32_t count = ts_node_named_child_count (node);
+  uint32_t i;
+  for (i = 0; i < count; i++)
+    {
+      TSNode subnode = ts_node_named_child (node, i);
+      if (ts_node_symbol (subnode) == ts_symbol_var_spec)
+        store_var_spec (subnode);
+    }
+}
+
+/* Processes a var_declaration node at the top level.  */
+static void
+store_var_declaration (TSNode node)
+{
+  uint32_t count = ts_node_named_child_count (node);
+  uint32_t i;
+  for (i = 0; i < count; i++)
+    {
+      TSNode subnode = ts_node_named_child (node, i);
+      if (ts_node_symbol (subnode) == ts_symbol_var_spec_list)
+        store_var_spec_list (subnode);
+      else if (ts_node_symbol (subnode) == ts_symbol_var_spec)
+        store_var_spec (subnode);
+    }
+}
+
+/* node is of type const_spec.  */
+static void
+store_const_spec (TSNode node)
+{
+  TSNode name_node = ts_node_child_by_field_id (node, ts_field_name);
+  string_desc_t name =
+    sd_new_addr (ts_node_end_byte (name_node) - ts_node_start_byte (name_node),
+                 (char *) contents + ts_node_start_byte (name_node));
+  #if DEBUG_GO && 0
+  fprintf (stderr, "Const name = %s\n", sd_c (name));
+  #endif
+  /* The type is always a predefined type.  */
+  go_type_t *type = &a_predeclared_type;
+  /* Store the const definition in current_package.globals.  */
+  hash_set_value (&current_package.globals,
+                  sd_data (name), sd_length (name),
+                  type);
+}
+
+/* Processes a const_declaration node at the top level.  */
+static void
+store_const_declaration (TSNode node)
+{
+  uint32_t count = ts_node_named_child_count (node);
+  uint32_t i;
+  for (i = 0; i < count; i++)
+    {
+      TSNode subnode = ts_node_named_child (node, i);
+      if (ts_node_symbol (subnode) == ts_symbol_const_spec)
+        store_const_spec (subnode);
+    }
+}
+
+/* Processes a function_declaration at the top level.  */
+static void
+store_function_declaration (TSNode node)
+{
+  TSNode name_node = ts_node_child_by_field_id (node, ts_field_name);
+  string_desc_t name =
+    sd_new_addr (ts_node_end_byte (name_node) - ts_node_start_byte (name_node),
+                 (char *) contents + ts_node_start_byte (name_node));
+  #if DEBUG_GO && 0
+  fprintf (stderr, "Func name = %s\n", sd_c (name));
+  #endif
+  go_type_t *type = get_type_from_function_or_method_node (node, NULL, false);
+  /* Store the func definition in current_package.globals.  */
+  hash_set_value (&current_package.globals,
+                  sd_data (name), sd_length (name),
+                  type);
+}
+
+static void
+store_top_level_declarations (TSNode root_node)
+{
+  uint32_t count = ts_node_named_child_count (root_node);
+  uint32_t i;
+  for (i = 0; i < count; i++)
+    {
+      TSNode node = ts_node_named_child (root_node, i);
+      if (ts_node_symbol (node) == ts_symbol_var_declaration)
+        store_var_declaration (node);
+      else if (ts_node_symbol (node) == ts_symbol_const_declaration)
+        store_const_declaration (node);
+      else if (ts_node_symbol (node) == ts_symbol_function_declaration)
+        store_function_declaration (node);
+    }
+}
+
+/* Initializes current_package.globals.  */
+static void
+init_current_package_globals (TSNode root_node)
+{
+  /* Initialize the hash table.  */
+  hash_init (&current_package.globals, 100);
+
+  /* Fill the current_package.globals table.  */
+  store_top_level_declarations (root_node);
+}
+
+/* --------- Go type analysis: Keeping track of local declarations --------- */
+
+/* node is of type type_declaration.  */
+static type_env_t
+augment_for_type_declaration (TSNode node, type_env_t tenv)
+{
+  /* Similar to store_type_declaration.  */
+  uint32_t count = ts_node_named_child_count (node);
+  uint32_t i;
+  for (i = 0; i < count; i++)
+    {
+      TSNode subnode = ts_node_named_child (node, i);
+      if (ts_node_symbol (subnode) == ts_symbol_type_alias
+          || ts_node_symbol (subnode) == ts_symbol_type_spec)
+        {
+          TSNode name_node = ts_node_child_by_field_id (subnode, ts_field_name);
+          if (ts_node_symbol (name_node) != ts_symbol_type_identifier)
+            abort ();
+          string_desc_t name =
+            sd_new_addr (ts_node_end_byte (name_node) - ts_node_start_byte (name_node),
+                         (char *) contents + ts_node_start_byte (name_node));
+          #if DEBUG_GO && 0
+          fprintf (stderr, "Local type name = %s\n", sd_c (name));
+          #endif
+          TSNode type_node = ts_node_child_by_field_id (subnode, ts_field_type);
+          go_type_t *type = get_type_from_type_node (type_node, tenv, false);
+          tenv = type_env_augment (tenv, name, type);
+        }
+    }
+  return tenv;
+}
+
+/* node is of type parameter_list.  */
+static variable_env_t
+augment_for_parameter_list (TSNode node, type_env_t tenv, variable_env_t venv)
+{
+  variable_env_t augmented_venv = venv;
+  uint32_t count = ts_node_named_child_count (node);
+  uint32_t i;
+  for (i = 0; i < count; i++)
+    {
+      TSNode subnode = ts_node_named_child (node, i);
+      if (ts_node_symbol (subnode) == ts_symbol_parameter_declaration)
+        {
+          uint32_t count2 = ts_node_named_child_count (subnode);
+          uint32_t j;
+          for (j = 0; j < count2; j++)
+            {
+              TSNode subsubnode = ts_node_named_child (subnode, j);
+              if (ts_node_symbol (subsubnode) == ts_symbol_identifier)
+                {
+                  TSNode name_node = subsubnode;
+                  string_desc_t name =
+                    sd_new_addr (ts_node_end_byte (name_node) - ts_node_start_byte (name_node),
+                                 (char *) contents + ts_node_start_byte (name_node));
+                  #if DEBUG_GO && 0
+                  fprintf (stderr, "Local parameter name = %s\n", sd_c (name));
+                  #endif
+                  TSNode type_node = ts_node_child_by_field_id (subnode, ts_field_type);
+                  go_type_t *type = get_type_from_type_node (type_node, tenv, false);
+                  augmented_venv = variable_env_augment (augmented_venv, name, type);
+                }
+            }
+        }
+      else if (ts_node_symbol (subnode) == ts_symbol_variadic_parameter_declaration)
+        {
+          uint32_t count2 = ts_node_named_child_count (subnode);
+          uint32_t j;
+          for (j = 0; j < count2; j++)
+            {
+              TSNode subsubnode = ts_node_named_child (subnode, j);
+              if (ts_node_symbol (subsubnode) == ts_symbol_identifier)
+                {
+                  TSNode name_node = subsubnode;
+                  string_desc_t name =
+                    sd_new_addr (ts_node_end_byte (name_node) - ts_node_start_byte (name_node),
+                                 (char *) contents + ts_node_start_byte (name_node));
+                  #if DEBUG_GO && 0
+                  fprintf (stderr, "Local variadic parameter name = %s\n", sd_c (name));
+                  #endif
+                  TSNode type_node = ts_node_child_by_field_id (subnode, ts_field_type);
+                  go_type_t *type = get_type_from_type_node (type_node, tenv, false);
+                  augmented_venv = variable_env_augment (augmented_venv, name, create_array_type (type));
+                }
+            }
+        }
+    }
+  return augmented_venv;
+}
+
+/* node is of type var_spec.  */
+static variable_env_t
+augment_for_var_spec (TSNode node, type_env_t tenv, variable_env_t venv)
+{
+  /* Similar to store_var_spec.  */
+  /* It may contain multiple names.  */
+  TSNode type_node = ts_node_child_by_field_id (node, ts_field_type);
+  if (!ts_node_is_null (type_node))
+    {
+      /* "If a type is present, each variable is given that type."  */
+      go_type_t *type = get_type_from_type_node (type_node, tenv, false);
+      uint32_t count = ts_node_named_child_count (node);
+      uint32_t i;
+      for (i = 0; i < count; i++)
+        {
+          TSNode subnode = ts_node_named_child (node, i);
+          if (ts_node_symbol (subnode) == ts_symbol_identifier)
+            {
+              TSNode name_node = subnode;
+              string_desc_t name =
+                sd_new_addr (ts_node_end_byte (name_node) - ts_node_start_byte (name_node),
+                             (char *) contents + ts_node_start_byte (name_node));
+              #if DEBUG_GO && 0
+              fprintf (stderr, "Local var name = %s\n", sd_c (name));
+              #endif
+              venv = variable_env_augment (venv, name, type);
+            }
+        }
+    }
+  else
+    {
+      /* "Otherwise, each variable is given the type of the corresponding
+          initialization value in the assignment."  */
+      uint32_t count = ts_node_named_child_count (node);
+      unsigned int mvcount;
+
+      mvcount = 0;
+      {
+        uint32_t i;
+        for (i = 0; i < count; i++)
+          {
+            TSNode subnode = ts_node_named_child (node, i);
+            if (ts_node_symbol (subnode) == ts_symbol_identifier)
+              mvcount++;
+          }
+      }
+      if (mvcount > 0)
+        {
+          /* We are in a context where mvcount values are expected.  */
+          TSNode value_node = ts_node_child_by_field_id (node, ts_field_value);
+          go_type_t **value_types = XNMALLOC (mvcount, go_type_t *);
+          unsigned int value_mvcount =
+            get_mvtypes_of_expression (mvcount, value_types, value_node, tenv, venv);
+          if (value_mvcount != mvcount)
+            {
+              unsigned int j;
+              for (j = 0; j < mvcount; j++)
+                value_types[j] = &unknown_type;
+            }
+          unsigned int j = 0;
+          uint32_t i;
+          for (i = 0; i < count; i++)
+            {
+              TSNode subnode = ts_node_named_child (node, i);
+              if (ts_node_symbol (subnode) == ts_symbol_identifier)
+                {
+                  TSNode name_node = subnode;
+                  string_desc_t name =
+                    sd_new_addr (ts_node_end_byte (name_node) - ts_node_start_byte (name_node),
+                                 (char *) contents + ts_node_start_byte (name_node));
+                  #if DEBUG_GO && 0
+                  fprintf (stderr, "Local var name = %s\n", sd_c (name));
+                  #endif
+                  venv = variable_env_augment (venv, name, value_types[j]);
+                  j++;
+                }
+            }
+          free (value_types);
+        }
+    }
+  return venv;
+}
+
+/* node is of type var_spec_list.  */
+static variable_env_t
+augment_for_var_spec_list (TSNode node, type_env_t tenv, variable_env_t venv)
+{
+  /* Similar to store_var_spec_list.  */
+  uint32_t count = ts_node_named_child_count (node);
+  uint32_t i;
+  for (i = 0; i < count; i++)
+    {
+      TSNode subnode = ts_node_named_child (node, i);
+      if (ts_node_symbol (subnode) == ts_symbol_var_spec)
+        venv = augment_for_var_spec (subnode, tenv, venv);
+    }
+  return venv;
+}
+
+/* node is of type var_declaration.  */
+static variable_env_t
+augment_for_variable_declaration (TSNode node, type_env_t tenv, variable_env_t venv)
+{
+  /* Similar to store_var_declaration.  */
+  uint32_t count = ts_node_named_child_count (node);
+  uint32_t i;
+  for (i = 0; i < count; i++)
+    {
+      TSNode subnode = ts_node_named_child (node, i);
+      if (ts_node_symbol (subnode) == ts_symbol_var_spec_list)
+        venv = augment_for_var_spec_list (subnode, tenv, venv);
+      else if (ts_node_symbol (subnode) == ts_symbol_var_spec)
+        venv = augment_for_var_spec (subnode, tenv, venv);
+    }
+  return venv;
+}
+
+/* node is of type const_spec.  */
+static variable_env_t
+augment_for_const_spec (TSNode node, type_env_t tenv, variable_env_t venv)
+{
+  /* Similar to store_const_spec.  */
+  TSNode name_node = ts_node_child_by_field_id (node, ts_field_name);
+  string_desc_t name =
+    sd_new_addr (ts_node_end_byte (name_node) - ts_node_start_byte (name_node),
+                 (char *) contents + ts_node_start_byte (name_node));
+  #if DEBUG_GO && 0
+  fprintf (stderr, "Local const name = %s\n", sd_c (name));
+  #endif
+  /* The type is always a predefined type.  */
+  go_type_t *type = &a_predeclared_type;
+  return variable_env_augment (venv, name, type);
+}
+
+/* node is of type const_declaration.  */
+static variable_env_t
+augment_for_const_declaration (TSNode node, type_env_t tenv, variable_env_t venv)
+{
+  /* Similar to store_const_declaration.  */
+  uint32_t count = ts_node_named_child_count (node);
+  uint32_t i;
+  for (i = 0; i < count; i++)
+    {
+      TSNode subnode = ts_node_named_child (node, i);
+      if (ts_node_symbol (subnode) == ts_symbol_const_spec)
+        venv = augment_for_const_spec (subnode, tenv, venv);
+    }
+  return venv;
+}
+
+/* node is of type short_var_declaration.  */
+static variable_env_t
+augment_for_short_variable_declaration (TSNode node, type_env_t tenv, variable_env_t venv)
+{
+  TSNode left_node = ts_node_child_by_field_id (node, ts_field_left);
+  if (ts_node_symbol (left_node) != ts_symbol_expression_list)
+    abort ();
+  unsigned int mvcount = ts_node_named_child_count (left_node);
+  go_type_t **mvtypes = XNMALLOC (mvcount, go_type_t *);
+  TSNode right_node = ts_node_child_by_field_id (node, ts_field_right);
+  if (ts_node_symbol (right_node) != ts_symbol_expression_list)
+    abort ();
+  /* We are in a context where mvcount values are expected.  */
+  unsigned int right_mvcount =
+    get_mvtypes_of_expression (mvcount, mvtypes, right_node, tenv, venv);
+  if (right_mvcount != mvcount)
+    {
+      unsigned int i;
+      for (i = 0; i < mvcount; i++)
+        mvtypes[i] = &unknown_type;
+    }
+  /* Now augment venv.  */
+  {
+    unsigned int i;
+    for (i = 0; i < mvcount; i++)
+      {
+        TSNode left_var_node = ts_node_named_child (left_node, i);
+        if (ts_node_symbol (left_var_node) == ts_symbol_identifier)
+          {
+            string_desc_t left_var_name =
+              sd_new_addr (ts_node_end_byte (left_var_node) - ts_node_start_byte (left_var_node),
+                           (char *) contents + ts_node_start_byte (left_var_node));
+            if (!sd_equals (left_var_name, sd_from_c ("_")))
+              venv = variable_env_augment (venv, left_var_name, mvtypes[i]);
+          }
+      }
+  }
+  free (mvtypes);
+  return venv;
+}
+
+/* -------------------------------- Comments -------------------------------- */
+
+/* These are for tracking whether comments count as immediately before
+   keyword.  */
+static int last_comment_line;
+static int last_non_comment_line;
+
+/* Saves a comment line.  */
+static void save_comment_line (string_desc_t gist)
+{
+  /* Remove leading whitespace.  */
+  while (sd_length (gist) > 0
+         && (sd_char_at (gist, 0) == ' '
+             || sd_char_at (gist, 0) == '\t'))
+    gist = sd_substring (gist, 1, sd_length (gist));
+  /* Remove trailing whitespace.  */
+  size_t len = sd_length (gist);
+  while (len > 0
+         && (sd_char_at (gist, len - 1) == ' '
+             || sd_char_at (gist, len - 1) == '\t'))
+    len--;
+  gist = sd_substring (gist, 0, len);
+  savable_comment_add (sd_c (gist));
+}
+
+/* Does the comment handling for NODE.
+   Updates savable_comment, last_comment_line, last_non_comment_line.
+   It is important that this function gets called
+     - for each node (not only the named nodes!),
+     - in depth-first traversal order.  */
+static void handle_comments (TSNode node, const char *contents)
+{
+  #if DEBUG_GO && 0
+  fprintf (stderr, "LCL=%d LNCL=%d node=[%s]|%s|\n", last_comment_line, last_non_comment_line, ts_node_type (node), ts_node_string (node));
+  #endif
+  if (last_comment_line < last_non_comment_line
+      && last_non_comment_line < ts_node_line_number (node))
+    /* We have skipped over a newline.  This newline terminated a line
+       with non-comment tokens, after the last comment line.  */
+    savable_comment_reset ();
+
+  if (ts_node_symbol (node) == ts_symbol_comment)
+    {
+      string_desc_t entire =
+        sd_new_addr (ts_node_end_byte (node) - ts_node_start_byte (node),
+                     (char *) contents + ts_node_start_byte (node));
+      /* It should either start with two slashes, or start and end with
+         the C comment markers.  */
+      if (sd_length (entire) >= 2
+          && sd_char_at (entire, 0) == '/'
+          && sd_char_at (entire, 1) == '/')
+        {
+          save_comment_line (sd_substring (entire, 2, sd_length (entire)));
+        }
+      else if (sd_length (entire) >= 4
+               && sd_char_at (entire, 0) == '/'
+               && sd_char_at (entire, 1) == '*'
+               && sd_char_at (entire, sd_length (entire) - 2) == '*'
+               && sd_char_at (entire, sd_length (entire) - 1) == '/')
+        {
+          string_desc_t gist = sd_substring (entire, 2, sd_length (entire) - 2);
+          /* Split into lines.
+             Remove leading and trailing whitespace from each line.  */
+          for (;;)
+            {
+              ptrdiff_t nl_index = sd_index (gist, '\n');
+              if (nl_index >= 0)
+                {
+                  save_comment_line (sd_substring (gist, 0, nl_index));
+                  gist = sd_substring (gist, nl_index + 1, sd_length (gist));
+                }
+              else
+                {
+                  save_comment_line (gist);
+                  break;
+                }
+            }
+        }
+      else
+        abort ();
+      last_comment_line = ts_node_end_point (node).row + 1;
+    }
+  else
+    last_non_comment_line = ts_node_line_number (node);
+}
+
+/* --------------------- Parsing and string extraction --------------------- */
+
+/* Context lookup table.  */
+static flag_context_list_table_ty *flag_context_list_table;
+
+/* Maximum supported nesting depth.  */
+#define MAX_NESTING_DEPTH 1000
+
+static int nesting_depth;
+
+/* The file is parsed into an abstract syntax tree.  Scan the syntax tree,
+   looking for a keyword in identifier position of a call_expression or
+   macro_invocation, followed by followed by a string among the arguments.
+   When we see this pattern, we have something to remember.
+
+     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.  */
+
+/* Forward declarations.  */
+static void extract_from_node (TSNode node,
+                               bool in_function,
+                               type_env_t tenv, variable_env_t venv,
+                               bool ignore,
+                               flag_region_ty *outer_region,
+                               message_list_ty *mlp);
+
+/* Extracts messages from the function call consisting of
+     - CALLEE_NODE: a tree node of type 'identifier' or 'selector_expression',
+     - ARGS_NODE: a tree node of type 'arguments'.
+   Extracted messages are added to MLP.  */
+static void
+extract_from_function_call (TSNode callee_node,
+                            TSNode args_node,
+                            type_env_t tenv, variable_env_t venv,
+                            flag_region_ty *outer_region,
+                            message_list_ty *mlp)
+{
+  uint32_t args_count = ts_node_child_count (args_node);
+
+  TSNode function_node;
+  if (ts_node_symbol (callee_node) == ts_symbol_identifier)
+    function_node = callee_node;
+  else if (ts_node_symbol (callee_node) == ts_symbol_selector_expression)
+    function_node = ts_node_child_by_field_id (callee_node, ts_field_field);
+  else
+    abort ();
+
+  if (!(ts_node_symbol (function_node) == ts_symbol_identifier
+        || ts_node_symbol (function_node) == ts_symbol_field_identifier))
+    abort ();
+
+  string_desc_t function_name =
+    sd_new_addr (ts_node_end_byte (function_node) - ts_node_start_byte (function_node),
+                 (char *) contents + ts_node_start_byte (function_node));
+
+  /* Context iterator.  */
+  flag_context_list_iterator_ty next_context_iter =
+    flag_context_list_iterator (
+      flag_context_list_table_lookup (
+        flag_context_list_table,
+        sd_data (function_name), sd_length (function_name)));
+
+  /* Information associated with the callee.  */
+  const struct callshapes *next_shapes = NULL;
+
+  if (ts_node_symbol (callee_node) == ts_symbol_identifier)
+    {
+      /* Look in the keywords table.  */
+      void *keyword_value;
+      if (hash_find_entry (&keywords,
+                           sd_data (function_name), sd_length (function_name),
+                           &keyword_value)
+          == 0)
+        next_shapes = (const struct callshapes *) keyword_value;
+    }
+  else if (ts_node_symbol (callee_node) == ts_symbol_selector_expression)
+    {
+      if (ts_node_symbol (function_node) != ts_symbol_field_identifier)
+        abort ();
+      TSNode operand_node = ts_node_child_by_field_id (callee_node, ts_field_operand);
+      /* If the operand is a package name, we have in fact a qualified identifier.  */
+      bool qualified_identifier = false;
+      if (ts_node_symbol (operand_node) == ts_symbol_identifier)
+        {
+          string_desc_t shortname =
+            sd_new_addr (ts_node_end_byte (operand_node) - ts_node_start_byte (operand_node),
+                         (char *) contents + ts_node_start_byte (operand_node));
+          /* Look up the package's full name.  */
+          void *found_package;
+          if (hash_find_entry (&package_table,
+                               sd_data (shortname), sd_length (shortname),
+                               &found_package)
+              == 0)
+            {
+              qualified_identifier = true;
+              /* The operand is a package name.  */
+              if (strcmp ((const char *) found_package, GOTEXT_PACKAGE_FULLNAME) == 0)
+                {
+                  /* Look in the gotext_keywords table.  */
+                  void *keyword_value;
+                  if (hash_find_entry (&gotext_keywords,
+                                       sd_data (function_name), sd_length (function_name),
+                                       &keyword_value)
+                      == 0)
+                    next_shapes = (const struct callshapes *) keyword_value;
+                }
+              else if (strcmp ((const char *) found_package, SNAPCORE_PACKAGE_FULLNAME) == 0)
+                {
+                  /* Look in the snapcore_keywords table.  */
+                  void *keyword_value;
+                  if (hash_find_entry (&snapcore_keywords,
+                                       sd_data (function_name), sd_length (function_name),
+                                       &keyword_value)
+                      == 0)
+                    next_shapes = (const struct callshapes *) keyword_value;
+                }
+              if (next_shapes == NULL)
+                {
+                  /* Look in the keywords table as well.  */
+                  void *keyword_value;
+                  if (hash_find_entry (&keywords,
+                                       sd_data (function_name), sd_length (function_name),
+                                       &keyword_value)
+                      == 0)
+                    next_shapes = (const struct callshapes *) keyword_value;
+                }
+            }
+        }
+      if (!qualified_identifier)
+        {
+          go_type_t *operand_type = get_type_of_expression (operand_node, tenv, venv);
+          if (operand_type->e == pointer)
+            operand_type = operand_type->u.eltype;
+          /* Here it is important that we can compare 'go_type_t *' pointers for equality.  */
+          hash_table *ht;
+          ht = (hash_table *) gl_map_get (gotext_type_keywords, operand_type);
+          if (ht == NULL)
+            ht = (hash_table *) gl_map_get (snapcore_type_keywords, operand_type);
+          if (ht != NULL)
+            {
+              /* Look in this hash table.  */
+              void *keyword_value;
+              if (hash_find_entry (ht,
+                                   sd_data (function_name), sd_length (function_name),
+                                   &keyword_value)
+                  == 0)
+                next_shapes = (const struct callshapes *) keyword_value;
+            }
+        }
+    }
+  else
+    abort ();
+
+  if (next_shapes != NULL)
+    {
+      /* We have a function, named by a relevant identifier, with an argument
+         list.  */
+
+      struct arglist_parser *argparser =
+        arglist_parser_alloc (mlp, next_shapes);
+
+      /* Current argument number.  */
+      uint32_t arg;
+      uint32_t i;
+
+      arg = 0;
+      for (i = 0; i < args_count; i++)
+        {
+          TSNode arg_node = ts_node_child (args_node, i);
+          handle_comments (arg_node, contents);
+          if (ts_node_is_named (arg_node)
+              && ts_node_symbol (arg_node) != ts_symbol_comment)
+            {
+              arg++;
+              flag_region_ty *arg_region =
+                inheriting_region (outer_region,
+                                   flag_context_list_iterator_advance (
+                                     &next_context_iter));
+
+              bool already_extracted = false;
+              if (is_string_literal (arg_node))
+                {
+                  lex_pos_ty pos;
+                  pos.file_name = logical_file_name;
+                  pos.line_number = ts_node_line_number (arg_node);
+
+                  char *string = string_literal_value (arg_node);
+
+                  if (extract_all)
+                    {
+                      remember_a_message (mlp, NULL, string, true, false,
+                                          arg_region, &pos,
+                                          NULL, savable_comment, true);
+                      already_extracted = true;
+                    }
+                  else
+                    {
+                      mixed_string_ty *mixed_string =
+                        mixed_string_alloc_utf8 (string, lc_string,
+                                                 pos.file_name, pos.line_number);
+                      arglist_parser_remember (argparser, arg, mixed_string,
+                                               arg_region,
+                                               pos.file_name, pos.line_number,
+                                               savable_comment, true);
+                    }
+                }
+
+              if (!already_extracted)
+                {
+                  if (++nesting_depth > MAX_NESTING_DEPTH)
+                    if_error (IF_SEVERITY_FATAL_ERROR,
+                              logical_file_name, ts_node_line_number (arg_node), (size_t)(-1), false,
+                              _("too many open parentheses"));
+                  extract_from_node (arg_node,
+                                     true,
+                                     tenv, venv,
+                                     false,
+                                     arg_region,
+                                     mlp);
+                  nesting_depth--;
+                }
+
+              unref_region (arg_region);
+            }
+        }
+      arglist_parser_done (argparser, arg);
+      return;
+    }
+
+  /* Recurse.  */
+
+  uint32_t i;
+
+  for (i = 0; i < args_count; i++)
+    {
+      TSNode arg_node = ts_node_child (args_node, i);
+      handle_comments (arg_node, contents);
+      if (ts_node_is_named (arg_node)
+          && ts_node_symbol (arg_node) != ts_symbol_comment)
+        {
+          flag_region_ty *arg_region =
+            inheriting_region (outer_region,
+                               flag_context_list_iterator_advance (
+                                 &next_context_iter));
+
+          if (++nesting_depth > MAX_NESTING_DEPTH)
+            if_error (IF_SEVERITY_FATAL_ERROR,
+                      logical_file_name, ts_node_line_number (arg_node), (size_t)(-1), false,
+                      _("too many open parentheses"));
+          extract_from_node (arg_node,
+                             true,
+                             tenv, venv,
+                             false,
+                             arg_region,
+                             mlp);
+          nesting_depth--;
+
+          unref_region (arg_region);
+        }
+    }
+}
+
+/* Extracts messages in the syntax tree NODE.
+   Extracted messages are added to MLP.  */
+static void
+extract_from_node (TSNode node,
+                   bool in_function,
+                   type_env_t tenv, variable_env_t venv,
+                   bool ignore,
+                   flag_region_ty *outer_region,
+                   message_list_ty *mlp)
+{
+  if (extract_all && !ignore && is_string_literal (node))
+    {
+      lex_pos_ty pos;
+      pos.file_name = logical_file_name;
+      pos.line_number = ts_node_line_number (node);
+
+      char *string = string_literal_value (node);
+
+      remember_a_message (mlp, NULL, string, true, false,
+                          outer_region, &pos,
+                          NULL, savable_comment, true);
+    }
+
+  if (ts_node_symbol (node) == ts_symbol_call_expression
+      && ts_node_named_child_count (node) >= 2)
+    {
+      TSNode callee_node = ts_node_named_child (node, 0);
+      /* This is the field called 'function'.  */
+      if (! ts_node_eq (ts_node_child_by_field_id (node, ts_field_function),
+                        callee_node))
+        abort ();
+      #if DEBUG_GO
+      fprintf (stderr, "callee_node = [%s]|%s|\n", ts_node_type (callee_node), ts_node_string (callee_node));
+      if (ts_node_symbol (callee_node) == ts_symbol_selector_expression)
+        {
+          TSNode operand_node = ts_node_child_by_field_id (callee_node, ts_field_operand);
+          string_desc_t operand_name =
+            sd_new_addr (ts_node_end_byte (operand_node) - ts_node_start_byte (operand_node),
+                         (char *) contents + ts_node_start_byte (operand_node));
+          fprintf (stderr, "operand_node = [%s]|%s| = %s\n", ts_node_type (operand_node), ts_node_string (operand_node), sd_c (operand_name));
+
+          TSNode field_node = ts_node_child_by_field_id (callee_node, ts_field_field);
+          string_desc_t field_name =
+            sd_new_addr (ts_node_end_byte (field_node) - ts_node_start_byte (field_node),
+                         (char *) contents + ts_node_start_byte (field_node));
+          fprintf (stderr, "field_node = [%s]|%s| = %s\n", ts_node_type (field_node), ts_node_string (field_node), sd_c (field_name));
+        }
+      #endif
+      if (ts_node_symbol (callee_node) == ts_symbol_identifier
+          || ts_node_symbol (callee_node) == ts_symbol_selector_expression)
+        {
+          TSNode args_node = ts_node_child_by_field_id (node, ts_field_arguments);
+          /* This is the field called 'arguments'.  */
+          if (ts_node_symbol (args_node) == ts_symbol_argument_list)
+            {
+              /* Handle the potential comments before the 'arguments'.  */
+              {
+                uint32_t count = ts_node_child_count (node);
+                uint32_t i;
+                for (i = 0; i < count; i++)
+                  {
+                    TSNode subnode = ts_node_child (node, i);
+                    if (ts_node_eq (subnode, args_node))
+                      break;
+                    handle_comments (subnode, contents);
+                  }
+              }
+              extract_from_function_call (callee_node, args_node,
+                                          tenv, venv,
+                                          outer_region,
+                                          mlp);
+              return;
+            }
+        }
+    }
+
+  #if DEBUG_GO && 0
+  if (ts_node_symbol (node) == ts_symbol_call_expression)
+    {
+      TSNode subnode = ts_node_child_by_field_id (node, ts_field_function);
+      fprintf (stderr, "-> %s\n", ts_node_string (subnode));
+      if (ts_node_symbol (subnode) == ts_symbol_identifier)
+        {
+          string_desc_t subnode_string =
+            sd_new_addr (ts_node_end_byte (subnode) - ts_node_start_byte (subnode),
+                         (char *) contents + ts_node_start_byte (subnode));
+          if (sd_equals (subnode_string, sd_from_c ("gettext")))
+            {
+              TSNode argsnode = ts_node_child_by_field_id (node, ts_field_arguments);
+              fprintf (stderr, "gettext arguments: %s\n", ts_node_string (argsnode));
+              fprintf (stderr, "gettext children:\n");
+              uint32_t count = ts_node_named_child_count (node);
+              uint32_t i;
+              for (i = 0; i < count; i++)
+                fprintf (stderr, "%u -> %s\n", i, ts_node_string (ts_node_named_child (node, i)));
+            }
+        }
+    }
+  #endif
+
+  /* Recurse.  */
+  if (ts_node_symbol (node) != ts_symbol_comment)
+    {
+      in_function = in_function
+                    || ts_node_symbol (node) == ts_symbol_function_declaration;
+      ignore = ignore
+               || (ts_node_symbol (node) == ts_symbol_import_declaration)
+               || is_string_literal (node);
+      uint32_t count = ts_node_child_count (node);
+      uint32_t i;
+      for (i = 0; i < count; i++)
+        {
+          TSNode subnode = ts_node_child (node, i);
+          handle_comments (subnode, contents);
+
+          #if DEBUG_GO
+          /* For debugging: Show the type of parenthesized expressions.  */
+          if (ts_node_symbol (subnode) == ts_symbol_parenthesized_expression)
+            {
+              print_type (get_type_of_expression (subnode, tenv, venv), stderr);
+              fprintf (stderr, "\n");
+            }
+          #endif
+
+          if (in_function
+              && ts_node_symbol (node) == ts_symbol_function_declaration
+              && ts_node_symbol (subnode) == ts_symbol_parameter_list)
+            {
+              /* Update venv.  */
+              venv = augment_for_parameter_list (subnode, tenv, venv);
+            }
+
+          if (++nesting_depth > MAX_NESTING_DEPTH)
+            if_error (IF_SEVERITY_FATAL_ERROR,
+                      logical_file_name, ts_node_line_number (subnode), (size_t)(-1), false,
+                      _("too many open parentheses"));
+          extract_from_node (subnode,
+                             in_function,
+                             tenv, venv,
+                             ignore,
+                             outer_region,
+                             mlp);
+          nesting_depth--;
+
+          if (in_function)
+            {
+              /* Update tenv and venv.  */
+              if (ts_node_symbol (subnode) == ts_symbol_type_declaration)
+                tenv = augment_for_type_declaration (subnode, tenv);
+              else if (ts_node_symbol (subnode) == ts_symbol_var_declaration)
+                venv = augment_for_variable_declaration (subnode, tenv, venv);
+              else if (ts_node_symbol (subnode) == ts_symbol_const_declaration)
+                venv = augment_for_const_declaration (subnode, tenv, venv);
+              else if (ts_node_symbol (subnode) == ts_symbol_short_var_declaration)
+                venv = augment_for_short_variable_declaration (subnode, tenv, venv);
+              else if (ts_node_symbol (subnode) == ts_symbol_for_clause)
+                {
+                  /* tree-sitter returns a 'for' statement as
+                     (for_statement (for_clause initializer: (short_var_declaration ...) ...) body: ...)
+                     However, the scope of the variables declared in the short_var_declaration
+                     is the entire for_statement, not just the for_clause.  */
+                  TSNode initializer_node = ts_node_child_by_field_id (subnode, ts_field_initializer);
+                  if (!ts_node_is_null (initializer_node)
+                      && ts_node_symbol (initializer_node) == ts_symbol_short_var_declaration)
+                    venv = augment_for_short_variable_declaration (initializer_node, tenv, venv);
+                }
+            }
+       }
+    }
+}
+
+void
+extract_go (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;
+
+  logical_file_name = xstrdup (logical_filename);
+
+  last_comment_line = -1;
+  last_non_comment_line = -1;
+
+  flag_context_list_table = flag_table;
+  nesting_depth = 0;
+
+  if (ts_language == NULL)
+    {
+      init_gotext_package ();
+      init_snapcore_package ();
+
+      init_keywords ();
+
+      ts_language = tree_sitter_go ();
+      ts_symbol_import_declaration =
+        ts_language_symbol ("import_declaration", true);
+      ts_symbol_import_spec_list =
+        ts_language_symbol ("import_spec_list", true);
+      ts_symbol_import_spec =
+        ts_language_symbol ("import_spec", true);
+      ts_symbol_package_identifier =
+        ts_language_symbol ("package_identifier", true);
+      ts_symbol_type_declaration =
+        ts_language_symbol ("type_declaration", true);
+      ts_symbol_type_alias =
+        ts_language_symbol ("type_alias", true);
+      ts_symbol_type_spec =
+        ts_language_symbol ("type_spec", true);
+      ts_symbol_type_identifier =
+        ts_language_symbol ("type_identifier", true);
+      ts_symbol_generic_type =
+        ts_language_symbol ("generic_type", true);
+      ts_symbol_qualified_type =
+        ts_language_symbol ("qualified_type", true);
+      ts_symbol_pointer_type =
+        ts_language_symbol ("pointer_type", true);
+      ts_symbol_struct_type =
+        ts_language_symbol ("struct_type", true);
+      ts_symbol_field_declaration_list =
+        ts_language_symbol ("field_declaration_list", true);
+      ts_symbol_field_declaration =
+        ts_language_symbol ("field_declaration", true);
+      ts_symbol_interface_type =
+        ts_language_symbol ("interface_type", true);
+      ts_symbol_method_elem =
+        ts_language_symbol ("method_elem", true);
+      ts_symbol_type_elem =
+        ts_language_symbol ("type_elem", true);
+      ts_symbol_array_type =
+        ts_language_symbol ("array_type", true);
+      ts_symbol_slice_type =
+        ts_language_symbol ("slice_type", true);
+      ts_symbol_map_type =
+        ts_language_symbol ("map_type", true);
+      ts_symbol_channel_type =
+        ts_language_symbol ("channel_type", true);
+      ts_symbol_function_type =
+        ts_language_symbol ("function_type", true);
+      ts_symbol_parameter_list =
+        ts_language_symbol ("parameter_list", true);
+      ts_symbol_parameter_declaration =
+        ts_language_symbol ("parameter_declaration", true);
+      ts_symbol_variadic_parameter_declaration =
+        ts_language_symbol ("variadic_parameter_declaration", true);
+      ts_symbol_negated_type =
+        ts_language_symbol ("negated_type", true);
+      ts_symbol_parenthesized_type =
+        ts_language_symbol ("parenthesized_type", true);
+      ts_symbol_var_declaration =
+        ts_language_symbol ("var_declaration", true);
+      ts_symbol_var_spec_list =
+        ts_language_symbol ("var_spec_list", true);
+      ts_symbol_var_spec =
+        ts_language_symbol ("var_spec", true);
+      ts_symbol_const_declaration =
+        ts_language_symbol ("const_declaration", true);
+      ts_symbol_const_spec =
+        ts_language_symbol ("const_spec", true);
+      ts_symbol_short_var_declaration =
+        ts_language_symbol ("short_var_declaration", true);
+      ts_symbol_expression_list =
+        ts_language_symbol ("expression_list", true);
+      ts_symbol_unary_expression =
+        ts_language_symbol ("unary_expression", true);
+      ts_symbol_binary_expression =
+        ts_language_symbol ("binary_expression", true);
+      ts_symbol_selector_expression =
+        ts_language_symbol ("selector_expression", true);
+      ts_symbol_index_expression =
+        ts_language_symbol ("index_expression", true);
+      ts_symbol_slice_expression =
+        ts_language_symbol ("slice_expression", true);
+      ts_symbol_call_expression =
+        ts_language_symbol ("call_expression", true);
+      ts_symbol_type_assertion_expression =
+        ts_language_symbol ("type_assertion_expression", true);
+      ts_symbol_type_conversion_expression =
+        ts_language_symbol ("type_conversion_expression", true);
+      ts_symbol_type_instantiation_expression =
+        ts_language_symbol ("type_instantiation_expression", true);
+      ts_symbol_composite_literal =
+        ts_language_symbol ("composite_literal", true);
+      ts_symbol_func_literal =
+        ts_language_symbol ("func_literal", true);
+      ts_symbol_int_literal =
+        ts_language_symbol ("int_literal", true);
+      ts_symbol_float_literal =
+        ts_language_symbol ("float_literal", true);
+      ts_symbol_imaginary_literal =
+        ts_language_symbol ("imaginary_literal", true);
+      ts_symbol_rune_literal =
+        ts_language_symbol ("rune_literal", true);
+      ts_symbol_nil =
+        ts_language_symbol ("nil", true);
+      ts_symbol_true =
+        ts_language_symbol ("true", true);
+      ts_symbol_false =
+        ts_language_symbol ("false", true);
+      ts_symbol_iota =
+        ts_language_symbol ("iota", true);
+      ts_symbol_parenthesized_expression =
+        ts_language_symbol ("parenthesized_expression", true);
+      ts_symbol_function_declaration =
+        ts_language_symbol ("function_declaration", true);
+      ts_symbol_for_clause =
+        ts_language_symbol ("for_clause", true);
+      ts_symbol_comment =
+        ts_language_symbol ("comment", true);
+      ts_symbol_raw_string_literal =
+        ts_language_symbol ("raw_string_literal", true);
+      ts_symbol_raw_string_literal_content =
+        ts_language_symbol ("raw_string_literal_content", true);
+      ts_symbol_interpreted_string_literal =
+        ts_language_symbol ("interpreted_string_literal", true);
+      ts_symbol_interpreted_string_literal_content =
+        ts_language_symbol ("interpreted_string_literal_content", true);
+      ts_symbol_escape_sequence =
+        ts_language_symbol ("escape_sequence", true);
+      ts_symbol_argument_list =
+        ts_language_symbol ("argument_list", true);
+      ts_symbol_identifier =
+        ts_language_symbol ("identifier", true);
+      ts_symbol_field_identifier =
+        ts_language_symbol ("field_identifier", true);
+      ts_symbol_dot =
+        ts_language_symbol ("dot", true);
+      ts_symbol_plus = ts_language_symbol ("+", false);
+      ts_field_path        = ts_language_field ("path");
+      ts_field_name        = ts_language_field ("name");
+      ts_field_package     = ts_language_field ("package");
+      ts_field_type        = ts_language_field ("type");
+      ts_field_element     = ts_language_field ("element");
+      ts_field_value       = ts_language_field ("value");
+      ts_field_result      = ts_language_field ("result");
+      ts_field_operator    = ts_language_field ("operator");
+      ts_field_left        = ts_language_field ("left");
+      ts_field_right       = ts_language_field ("right");
+      ts_field_function    = ts_language_field ("function");
+      ts_field_arguments   = ts_language_field ("arguments");
+      ts_field_operand     = ts_language_field ("operand");
+      ts_field_field       = ts_language_field ("field");
+      ts_field_initializer = ts_language_field ("initializer");
+    }
+
+  /* Read the file into memory.  */
+  char *contents_data;
+  size_t contents_length;
+  contents_data = read_file (real_filename, 0, &contents_length);
+  if (contents_data == NULL)
+    error (EXIT_FAILURE, errno, _("error while reading \"%s\""),
+           real_filename);
+
+  /* tree-sitter works only on files whose size fits in an uint32_t.  */
+  if (contents_length > 0xFFFFFFFFUL)
+    error (EXIT_FAILURE, 0, _("file \"%s\" is unsupported because too large"),
+           real_filename);
+
+  /* Go source files are UTF-8 encoded.
+     <https://go.dev/ref/spec#Source_code_representation>  */
+  if (u8_check ((uint8_t *) contents_data, contents_length) != NULL)
+    error (EXIT_FAILURE, 0,
+           _("file \"%s\" is invalid because not UTF-8 encoded"),
+           real_filename);
+  xgettext_current_source_encoding = po_charset_utf8;
+
+  /* Create a parser.  */
+  TSParser *parser = ts_parser_new ();
+
+  /* Set the parser's language.  */
+  ts_parser_set_language (parser, ts_language);
+
+  /* Parse the file, producing a syntax tree.  */
+  TSTree *tree = ts_parser_parse_string (parser, NULL, contents_data, contents_length);
+
+  #if DEBUG_GO
+  /* For debugging: Print the tree.  */
+  {
+    char *tree_as_string = ts_node_string (ts_tree_root_node (tree));
+    fprintf (stderr, "Syntax tree: %s\n", tree_as_string);
+    free (tree_as_string);
+  }
+  #endif
+
+  contents = contents_data;
+
+  init_package_table (ts_tree_root_node (tree));
+
+  init_current_package_types (ts_tree_root_node (tree));
+  init_current_package_globals (ts_tree_root_node (tree));
+
+  extract_from_node (ts_tree_root_node (tree),
+                     false,
+                     NULL, NULL,
+                     false,
+                     null_context_region (),
+                     mlp);
+
+  ts_tree_delete (tree);
+  ts_parser_delete (parser);
+  free (contents_data);
+
+  logical_file_name = NULL;
+}
diff --git a/gettext-tools/src/x-go.h b/gettext-tools/src/x-go.h
new file mode 100644 (file)
index 0000000..2206ebb
--- /dev/null
@@ -0,0 +1,52 @@
+/* xgettext Go backend.
+   Copyright (C) 2002-2025 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 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 <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <bruno@clisp.org>, 2025.  */
+
+
+#include <stdio.h>
+
+#include "message.h"
+#include "xg-arglist-context.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#define EXTENSIONS_GO \
+  { "go",     "Go" },                                                   \
+
+#define SCANNERS_GO \
+  { "Go",               extract_go, NULL,                               \
+                        &flag_table_go, &formatstring_go, NULL },       \
+
+/* Scan a Go file and add its translatable strings to mdlp.  */
+extern void extract_go (FILE *fp, const char *real_filename,
+                        const char *logical_filename,
+                        flag_context_list_table_ty *flag_table,
+                        msgdomain_list_ty *mdlp);
+
+extern void x_go_keyword (const char *keyword);
+extern void x_go_extract_all (void);
+
+extern void init_flag_table_go (void);
+
+
+#ifdef __cplusplus
+}
+#endif
index a87f0325e884be0a5fa2626695bcb1bc28a6fff3..c26dd0b0317a3b695bb27e044f3ff592096a43ae 100644 (file)
 #include "x-elisp.h"
 #include "x-librep.h"
 #include "x-rust.h"
+#include "x-go.h"
 #include "x-ruby.h"
 #include "x-sh.h"
 #include "x-awk.h"
@@ -201,6 +202,7 @@ static flag_context_list_table_ty flag_table_librep;
 extern flag_context_list_table_ty flag_table_rust_functions;
 extern flag_context_list_table_ty flag_table_rust_macros;
 #endif
+static flag_context_list_table_ty flag_table_go;
 static flag_context_list_table_ty flag_table_ruby;
 static flag_context_list_table_ty flag_table_sh;
 static flag_context_list_table_ty flag_table_awk;
@@ -395,6 +397,7 @@ main (int argc, char *argv[])
   init_flag_table_elisp ();
   init_flag_table_librep ();
   init_flag_table_rust ();
+  init_flag_table_go ();
   init_flag_table_ruby ();
   init_flag_table_sh ();
   init_flag_table_awk ();
@@ -429,6 +432,7 @@ main (int argc, char *argv[])
         x_perl_extract_all ();
         x_php_extract_all ();
         x_rust_extract_all ();
+        x_go_extract_all ();
         x_ruby_extract_all ();
         x_lua_extract_all ();
         x_javascript_extract_all ();
@@ -510,6 +514,7 @@ main (int argc, char *argv[])
         x_perl_keyword (optarg);
         x_php_keyword (optarg);
         x_rust_keyword (optarg);
+        x_go_keyword (optarg);
         x_ruby_keyword (optarg);
         x_lua_keyword (optarg);
         x_javascript_keyword (optarg);
@@ -1133,7 +1138,7 @@ Choice of input file language:\n"));
   -L, --language=NAME         recognise the specified language\n\
                                 (C, C++, ObjectiveC, PO, Python, Java,\n\
                                 JavaProperties, C#, JavaScript, Scheme, Guile,\n\
-                                Lisp, EmacsLisp, librep, Rust, Ruby, Shell,\n\
+                                Lisp, EmacsLisp, librep, Rust, Go, Ruby, Shell,\n\
                                 awk, Lua, Smalltalk, Vala, Tcl, Perl, PHP,\n\
                                 GCC-source, YCP, NXStringTable, RST, RSJ,\n\
                                 Glade, GSettings, Desktop)\n"));
@@ -1177,25 +1182,26 @@ Language specific options:\n"));
       printf (_("\
                                 (only languages C, C++, ObjectiveC, Python,\n\
                                 Java, C#, JavaScript, Scheme, Guile, Lisp,\n\
-                                EmacsLisp, librep, Rust, Shell, awk, Lua, Vala,\n\
-                                Tcl, Perl, PHP, GCC-source, Glade, GSettings)\n"));
+                                EmacsLisp, librep, Rust, Go, Shell, awk, Lua,\n\
+                                Vala, Tcl, Perl, PHP, GCC-source, Glade,\n\
+                                GSettings)\n"));
       printf (_("\
   -kWORD, --keyword=WORD      look for WORD as an additional keyword\n\
   -k, --keyword               do not to use default keywords\n"));
       printf (_("\
                                 (only languages C, C++, ObjectiveC, Python,\n\
                                 Java, C#, JavaScript, Scheme, Guile, Lisp,\n\
-                                EmacsLisp, librep, Rust, Shell, awk, Lua, Vala,\n\
-                                Tcl, Perl, PHP, GCC-source, Glade, GSettings,\n\
-                                Desktop)\n"));
+                                EmacsLisp, librep, Rust, Go, Shell, awk, Lua,\n\
+                                Vala, Tcl, Perl, PHP, GCC-source, Glade,\n\
+                                GSettings, Desktop)\n"));
       printf (_("\
       --flag=WORD:ARG:FLAG    additional flag for strings inside the argument\n\
                               number ARG of keyword WORD\n"));
       printf (_("\
                                 (only languages C, C++, ObjectiveC, Python,\n\
                                 Java, C#, JavaScript, Scheme, Guile, Lisp,\n\
-                                EmacsLisp, librep, Rust, Shell, awk, Lua, Vala,\n\
-                                Tcl, Perl, PHP, GCC-source, YCP)\n"));
+                                EmacsLisp, librep, Rust, Go, Shell, awk, Lua,\n\
+                                Vala, Tcl, Perl, PHP, GCC-source, YCP)\n"));
       printf (_("\
       --tag=WORD:FORMAT       defines the behaviour of tagged template literals\n\
                               with tag WORD\n"));
@@ -1689,6 +1695,11 @@ xgettext_record_flag (const char *optionstring)
                                                       name_start, name_end,
                                                       argnum, value, pass);
                     break;
+                  case format_go:
+                    flag_context_list_table_insert (&flag_table_go, XFORMAT_PRIMARY,
+                                                    name_start, name_end,
+                                                    argnum, value, pass);
+                    break;
                   case format_ruby:
                     flag_context_list_table_insert (&flag_table_ruby, XFORMAT_PRIMARY,
                                                     name_start, name_end,
@@ -2313,6 +2324,7 @@ language_to_extractor (const char *name)
     SCANNERS_ELISP
     SCANNERS_LIBREP
     SCANNERS_RUST
+    SCANNERS_GO
     SCANNERS_RUBY
     SCANNERS_SH
     SCANNERS_AWK
@@ -2407,6 +2419,7 @@ extension_to_language (const char *extension)
     EXTENSIONS_ELISP
     EXTENSIONS_LIBREP
     EXTENSIONS_RUST
+    EXTENSIONS_GO
     EXTENSIONS_RUBY
     EXTENSIONS_SH
     EXTENSIONS_AWK
index b48b6d37db3558a357a001c8e375eb1be844b04d..ab8710d11dce34abe547e47e7909a2a7f07b878d 100644 (file)
@@ -112,6 +112,11 @@ TESTS = gettext-1 gettext-2 \
        xgettext-elisp-stackovfl-3 xgettext-elisp-stackovfl-4 \
        xgettext-glade-1 xgettext-glade-2 xgettext-glade-3 xgettext-glade-4 \
        xgettext-glade-5 xgettext-glade-6 xgettext-glade-7 \
+       xgettext-go-1 xgettext-go-2 xgettext-go-3 xgettext-go-4 xgettext-go-5 \
+       xgettext-go-6 xgettext-go-7 xgettext-go-8 xgettext-go-9 xgettext-go-10 \
+       xgettext-go-11 xgettext-go-12 xgettext-go-13 xgettext-go-14 \
+       xgettext-go-15 xgettext-go-16 xgettext-go-17 xgettext-go-18 \
+       xgettext-go-stackovfl-1 xgettext-go-stackovfl-2 \
        xgettext-gsettings-1 \
        xgettext-its-1 xgettext-its-2 \
        xgettext-java-1 xgettext-java-2 xgettext-java-3 xgettext-java-4 \
@@ -191,6 +196,7 @@ TESTS = gettext-1 gettext-2 \
        format-elisp-1 format-elisp-2 \
        format-gcc-internal-1 format-gcc-internal-2 \
        format-gfc-internal-1 format-gfc-internal-2 \
+       format-go-1 format-go-2 \
        format-java-1 format-java-2 \
        format-java-printf-1 format-java-printf-2 \
        format-javascript-1 format-javascript-2 \
diff --git a/gettext-tools/tests/format-go-1 b/gettext-tools/tests/format-go-1
new file mode 100755 (executable)
index 0000000..396fd7c
--- /dev/null
@@ -0,0 +1,181 @@
+#! /bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test recognition of Go format strings.
+
+cat <<\EOF > f-go-1.data
+# Valid: no argument
+"abc%%"
+# Valid: one generic value argument
+"abc%v"
+# Valid: one generic value argument
+"abc%T"
+# Valid: one boolean argument
+"abc%t"
+# Valid: one character argument
+"abc%c"
+# Valid: one character argument
+"abc%U"
+# Valid: one string argument
+"abc%s"
+# Valid: one character or string argument
+"abc%q"
+# Valid: one floating-point argument
+"abc%e"
+# Valid: one floating-point argument
+"abc%E"
+# Valid: one floating-point argument
+"abc%f"
+# Valid: one floating-point argument
+"abc%F"
+# Valid: one floating-point argument
+"abc%g"
+# Valid: one floating-point argument
+"abc%G"
+# Valid: one integer argument
+"abc%O"
+# Valid: one integer or pointer argument
+"abc%d"
+# Valid: one integer or pointer argument
+"abc%o"
+# Valid: one integer or floating-point or pointer argument
+"abc%b"
+# Valid: one integer or floating-point or string or pointer argument
+"abc%x"
+# Valid: one integer or floating-point or string or pointer argument
+"abc%X"
+# Valid: one argument with flags
+"abc%#0d"
+# Valid: one argument with width
+"abc%2d"
+# Valid: one argument with precision
+"abc%.4d"
+# Valid: one argument with width and precision
+"abc%14.4d"
+# Invalid: unterminated
+"abc%"
+# Invalid: unknown format specifier
+"abc%y"
+# Invalid: flags after width
+"abc%2#d"
+# Invalid: twice precision
+"abc%.4.2d"
+# Valid: three arguments
+"abc%d%x%x"
+# Valid: a numbered argument
+"abc%[1]d"
+# Invalid: argument number zero
+"abc%[0]d"
+# Valid: a numbered argument
+"abc%[1000000]d"
+# Invalid: argument number too large
+"abc%[1000001]d"
+# Invalid: unterminated argument number
+"abc%["
+# Invalid: unterminated argument number
+"abc%[1"
+# Invalid: unterminated directive
+"abc%[1]"
+# Valid: width is a numbered argument
+"abc%[1]*d"
+# Invalid: width is argument number zero
+"abc%[0]*d"
+# Valid: width is a numbered argument
+"abc%[1000000]*d"
+# Invalid: width argument number too large
+"abc%[1000001]*d"
+# Valid: precision is a numbered argument
+"abc%.[1]*d"
+# Invalid: precision is argument number zero
+"abc%.[0]*d"
+# Valid: precision is a numbered argument
+"abc%.[1000000]*d"
+# Invalid: precision argument number too large
+"abc%.[1000001]*d"
+# Invalid: unterminated precision argument number
+"abc%.["
+# Invalid: unterminated precision argument number
+"abc%.[1"
+# Invalid: unterminated directive
+"abc%.[1]"
+# Valid: width and precision are numbered arguments
+"abc%[2]*.[1]*d"
+# Valid: width and main are numbered arguments
+"abc%[2]*[1]d"
+# Valid: precision and main are numbered arguments
+"abc%.[2]*[1]d"
+# Valid: width, precision, and main are numbered arguments
+"abc%[3]*.[2]*[1]d"
+# Invalid syntax of numbered arguments
+"abc%[3].[2]*[1]d"
+# Invalid syntax of numbered arguments
+"abc%[3]*.[2][1]d"
+# Invalid syntax of numbered arguments
+"abc%[3][3]*.[2]*[1]d"
+# Invalid syntax of numbered arguments
+"abc%[3]*.[2][2]*[1]d"
+# Invalid syntax of numbered arguments / twice precision
+"abc%[3]*.[2].[2]*[1]d"
+# Invalid syntax of numbered arguments
+"abc%[3]*.[2]*[1][1]d"
+# Valid: flags before number
+"abc%#[1]d"
+# Invalid: flags after number
+"abc%[1]#d"
+# Valid: three arguments, two with same number
+"abc%[1]x,%[2]c,%[1]X"
+# Invalid: argument with conflicting types
+"abc%[1]o,%[2]c,%[1]s"
+# Valid: no conflict
+"abc%[1]x,%[2]c,%[1]s"
+# Valid: mixing of numbered and unnumbered arguments
+"abc%d%[2]x"
+# Valid: mixing of numbered and unnumbered arguments
+"abc%[5]d%x"
+# Valid: numbered argument with constant precision
+"abc%.9[1]x"
+# Invalid: argument number before precision
+"abc%[1].9x"
+# Valid: missing non-final argument
+"abc%[2]x%[3]s"
+# Valid: permutation
+"abc%[2]ddef%[1]d"
+# Valid: multiple uses of same argument
+"abc%[2]xdef%[1]qghi%[2]x"
+EOF
+
+: ${XGETTEXT=xgettext}
+n=0
+while read comment; do
+  read string
+  n=`expr $n + 1`
+  cat <<EOF > f-go-1-$n.in
+Gettext(${string});
+EOF
+  ${XGETTEXT} -L Go -o f-go-1-$n.po f-go-1-$n.in || Exit 1
+  test -f f-go-1-$n.po || Exit 1
+  fail=
+  if echo "$comment" | grep 'Valid:' > /dev/null; then
+    if grep go-format f-go-1-$n.po > /dev/null; then
+      :
+    else
+      fail=yes
+    fi
+  else
+    if grep go-format f-go-1-$n.po > /dev/null; then
+      fail=yes
+    else
+      :
+    fi
+  fi
+  if test -n "$fail"; then
+    echo "Format string recognition error:" 1>&2
+    cat f-go-1-$n.in 1>&2
+    echo "Got:" 1>&2
+    cat f-go-1-$n.po 1>&2
+    Exit 1
+  fi
+  rm -f f-go-1-$n.in f-go-1-$n.po
+done < f-go-1.data
+
+Exit 0
diff --git a/gettext-tools/tests/format-go-2 b/gettext-tools/tests/format-go-2
new file mode 100755 (executable)
index 0000000..987d1fc
--- /dev/null
@@ -0,0 +1,523 @@
+#! /bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test checking of Go format strings.
+
+cat <<\EOF > f-go-2.data
+# Valid: %% doesn't count
+msgid  "abc%%def"
+msgstr "xyz"
+# Invalid: invalid msgstr
+msgid  "abc%%def"
+msgstr "xyz%"
+# Valid: same arguments
+msgid  "abc%s%xdef"
+msgstr "xyz%s%x"
+# Valid: same arguments, with different widths
+msgid  "abc%2sdef"
+msgstr "xyz%3s"
+# Valid: same arguments but in numbered syntax
+msgid  "abc%s%xdef"
+msgstr "xyz%[1]s%[2]x"
+# Valid: permutation
+msgid  "abc%s%x%cdef"
+msgstr "xyz%[3]c%[2]x%[1]s"
+# Invalid: too few arguments
+msgid  "abc%[2]xdef%[1]s"
+msgstr "xyz%[1]]s"
+# Invalid: too few arguments
+msgid  "abc%sdef%x"
+msgstr "xyz%s"
+# Invalid: too many arguments
+msgid  "abc%xdef"
+msgstr "xyz%xvw%c"
+# Valid: same numbered arguments, with different widths
+msgid  "abc%5[2]s%4[1]s"
+msgstr "xyz%4[2]s%5[1]s"
+# Invalid: missing argument
+msgid  "abc%[2]sdef%[1]x"
+msgstr "xyz%[1]x"
+# Invalid: missing argument
+msgid  "abc%[1]sdef%[2]x"
+msgstr "xyz%[2]x"
+# Invalid: added argument
+msgid  "abc%[1]xdef"
+msgstr "xyz%[1]xvw%[2]c"
+# Valid: uses arguments 1, 2
+msgid  "abc%[1]d%d"
+msgstr "xyz%d%d"
+# Valid: uses argument 1
+msgid  "abc%d%[1]d"
+msgstr "xyz%d"
+# Valid: uses arguments 2, 3
+msgid  "abc%[2]d%d"
+msgstr "xyz%[3]d%[2]d"
+# Invalid: uses arguments 2, 3
+msgid  "abc%[2]d%d"
+msgstr "xyz%d%d%d"
+# Valid: uses arguments 1, 2
+msgid  "abc%d%[2]d"
+msgstr "xyz%d%d"
+# Valid: uses argument 2
+msgid  "abc%[2]d%[2]d"
+msgstr "xyz%[2]d"
+# Invalid: uses argument 2
+msgid  "abc%[2]d%[2]d"
+msgstr "xyz%d%d"
+# Valid: uses arguments 1, 2
+msgid  "abc%[1]d%d%[2]d"
+msgstr "xyz%d%d"
+# Valid: uses arguments 1, 2
+msgid  "abc%d%[1]d%[2]d"
+msgstr "xyz%d%d"
+# Valid: uses arguments 1, 2, 3
+msgid  "abc%[2]d%d%[1]d"
+msgstr "xyz%d%d%d"
+# Valid: uses arguments 1, 2
+msgid  "abc%d%[2]d%[1]d"
+msgstr "xyz%d%d"
+# Valid: uses arguments 1, 2, 3
+msgid  "abc%[1]d%[2]d%d"
+msgstr "xyz%d%d%d"
+# Valid: uses arguments 1, 2
+msgid  "abc%[2]d%[1]d%d"
+msgstr "xyz%d%d"
+# Valid: type compatibility
+msgid  "abc%c"
+msgstr "xyz%U"
+# Valid: type compatibility
+msgid  "abc%e"
+msgstr "xyz%E"
+# Valid: type compatibility
+msgid  "abc%e"
+msgstr "xyz%f"
+# Valid: type compatibility
+msgid  "abc%e"
+msgstr "xyz%F"
+# Valid: type compatibility
+msgid  "abc%e"
+msgstr "xyz%g"
+# Valid: type compatibility
+msgid  "abc%e"
+msgstr "xyz%G"
+# Valid: type compatibility
+msgid  "abc%d"
+msgstr "xyz%o"
+# Valid: type compatibility
+msgid  "abc%x"
+msgstr "xyz%X"
+# Invalid: type incompatibility
+msgid  "abc%v"
+msgstr "xyz%T"
+# Invalid: type incompatibility
+msgid  "abc%v"
+msgstr "xyz%t"
+# Invalid: type incompatibility
+msgid  "abc%v"
+msgstr "xyz%c"
+# Invalid: type incompatibility
+msgid  "abc%v"
+msgstr "xyz%s"
+# Invalid: type incompatibility
+msgid  "abc%v"
+msgstr "xyz%q"
+# Invalid: type incompatibility
+msgid  "abc%v"
+msgstr "xyz%e"
+# Invalid: type incompatibility
+msgid  "abc%v"
+msgstr "xyz%O"
+# Invalid: type incompatibility
+msgid  "abc%v"
+msgstr "xyz%d"
+# Invalid: type incompatibility
+msgid  "abc%v"
+msgstr "xyz%b"
+# Invalid: type incompatibility
+msgid  "abc%v"
+msgstr "xyz%x"
+# Invalid: type incompatibility
+msgid  "abc%T"
+msgstr "xyz%t"
+# Invalid: type incompatibility
+msgid  "abc%T"
+msgstr "xyz%c"
+# Invalid: type incompatibility
+msgid  "abc%T"
+msgstr "xyz%s"
+# Invalid: type incompatibility
+msgid  "abc%T"
+msgstr "xyz%q"
+# Invalid: type incompatibility
+msgid  "abc%T"
+msgstr "xyz%e"
+# Invalid: type incompatibility
+msgid  "abc%T"
+msgstr "xyz%O"
+# Invalid: type incompatibility
+msgid  "abc%T"
+msgstr "xyz%d"
+# Invalid: type incompatibility
+msgid  "abc%T"
+msgstr "xyz%b"
+# Invalid: type incompatibility
+msgid  "abc%T"
+msgstr "xyz%x"
+# Invalid: type incompatibility
+msgid  "abc%t"
+msgstr "xyz%c"
+# Invalid: type incompatibility
+msgid  "abc%t"
+msgstr "xyz%s"
+# Invalid: type incompatibility
+msgid  "abc%t"
+msgstr "xyz%q"
+# Invalid: type incompatibility
+msgid  "abc%t"
+msgstr "xyz%e"
+# Invalid: type incompatibility
+msgid  "abc%t"
+msgstr "xyz%O"
+# Invalid: type incompatibility
+msgid  "abc%t"
+msgstr "xyz%d"
+# Invalid: type incompatibility
+msgid  "abc%t"
+msgstr "xyz%b"
+# Invalid: type incompatibility
+msgid  "abc%t"
+msgstr "xyz%x"
+# Invalid: type incompatibility
+msgid  "abc%c"
+msgstr "xyz%s"
+# Invalid: type incompatibility
+msgid  "abc%c"
+msgstr "xyz%q"
+# Invalid: type incompatibility
+msgid  "abc%c"
+msgstr "xyz%e"
+# Invalid: type incompatibility
+msgid  "abc%c"
+msgstr "xyz%O"
+# Invalid: type incompatibility
+msgid  "abc%c"
+msgstr "xyz%d"
+# Invalid: type incompatibility
+msgid  "abc%c"
+msgstr "xyz%b"
+# Invalid: type incompatibility
+msgid  "abc%c"
+msgstr "xyz%x"
+# Invalid: type incompatibility
+msgid  "abc%s"
+msgstr "xyz%q"
+# Invalid: type incompatibility
+msgid  "abc%s"
+msgstr "xyz%e"
+# Invalid: type incompatibility
+msgid  "abc%s"
+msgstr "xyz%O"
+# Invalid: type incompatibility
+msgid  "abc%s"
+msgstr "xyz%d"
+# Invalid: type incompatibility
+msgid  "abc%s"
+msgstr "xyz%b"
+# Invalid: type incompatibility
+msgid  "abc%s"
+msgstr "xyz%x"
+# Invalid: type incompatibility
+msgid  "abc%q"
+msgstr "xyz%e"
+# Invalid: type incompatibility
+msgid  "abc%q"
+msgstr "xyz%O"
+# Invalid: type incompatibility
+msgid  "abc%q"
+msgstr "xyz%d"
+# Invalid: type incompatibility
+msgid  "abc%q"
+msgstr "xyz%b"
+# Invalid: type incompatibility
+msgid  "abc%q"
+msgstr "xyz%x"
+# Invalid: type incompatibility
+msgid  "abc%e"
+msgstr "xyz%O"
+# Invalid: type incompatibility
+msgid  "abc%e"
+msgstr "xyz%d"
+# Invalid: type incompatibility
+msgid  "abc%e"
+msgstr "xyz%b"
+# Invalid: type incompatibility
+msgid  "abc%e"
+msgstr "xyz%x"
+# Invalid: type incompatibility
+msgid  "abc%O"
+msgstr "xyz%d"
+# Invalid: type incompatibility
+msgid  "abc%O"
+msgstr "xyz%b"
+# Invalid: type incompatibility
+msgid  "abc%O"
+msgstr "xyz%x"
+# Invalid: type incompatibility
+msgid  "abc%d"
+msgstr "xyz%b"
+# Invalid: type incompatibility
+msgid  "abc%d"
+msgstr "xyz%x"
+# Invalid: type incompatibility
+msgid  "abc%b"
+msgstr "xyz%x"
+# Valid: intersected type compatibility
+msgid  "abc%t"
+msgstr "xyz%[1]v%[1]t"
+# Valid: intersected type compatibility
+msgid  "abc%c"
+msgstr "xyz%[1]v%[1]c"
+# Valid: intersected type compatibility
+msgid  "abc%s"
+msgstr "xyz%[1]v%[1]s"
+# Valid: intersected type compatibility
+msgid  "abc%q"
+msgstr "xyz%[1]v%[1]q"
+# Valid: intersected type compatibility
+msgid  "abc%e"
+msgstr "xyz%[1]v%[1]e"
+# Valid: intersected type compatibility
+msgid  "abc%O"
+msgstr "xyz%[1]v%[1]O"
+# Valid: intersected type compatibility
+msgid  "abc%d"
+msgstr "xyz%[1]v%[1]d"
+# Valid: intersected type compatibility
+msgid  "abc%b"
+msgstr "xyz%[1]v%[1]b"
+# Valid: intersected type compatibility
+msgid  "abc%x"
+msgstr "xyz%[1]v%[1]x"
+# Invalid: intersected type incompatibility
+msgid  "abc%t"
+msgstr "xyz%[1]t%[1]c"
+# Invalid: intersected type incompatibility
+msgid  "abc%c"
+msgstr "xyz%[1]t%[1]c"
+# Invalid: intersected type incompatibility
+msgid  "abc%t"
+msgstr "xyz%[1]t%[1]s"
+# Invalid: intersected type incompatibility
+msgid  "abc%s"
+msgstr "xyz%[1]t%[1]s"
+# Invalid: intersected type incompatibility
+msgid  "abc%t"
+msgstr "xyz%[1]t%[1]q"
+# Invalid: intersected type incompatibility
+msgid  "abc%q"
+msgstr "xyz%[1]t%[1]q"
+# Invalid: intersected type incompatibility
+msgid  "abc%t"
+msgstr "xyz%[1]t%[1]e"
+# Invalid: intersected type incompatibility
+msgid  "abc%e"
+msgstr "xyz%[1]t%[1]e"
+# Invalid: intersected type incompatibility
+msgid  "abc%t"
+msgstr "xyz%[1]t%[1]O"
+# Invalid: intersected type incompatibility
+msgid  "abc%O"
+msgstr "xyz%[1]t%[1]O"
+# Invalid: intersected type incompatibility
+msgid  "abc%t"
+msgstr "xyz%[1]t%[1]d"
+# Invalid: intersected type incompatibility
+msgid  "abc%d"
+msgstr "xyz%[1]t%[1]d"
+# Invalid: intersected type incompatibility
+msgid  "abc%t"
+msgstr "xyz%[1]t%[1]b"
+# Invalid: intersected type incompatibility
+msgid  "abc%b"
+msgstr "xyz%[1]t%[1]b"
+# Invalid: intersected type incompatibility
+msgid  "abc%t"
+msgstr "xyz%[1]t%[1]x"
+# Invalid: intersected type incompatibility
+msgid  "abc%x"
+msgstr "xyz%[1]t%[1]x"
+# Invalid: intersected type incompatibility
+msgid  "abc%c"
+msgstr "xyz%[1]c%[1]s"
+# Invalid: intersected type incompatibility
+msgid  "abc%s"
+msgstr "xyz%[1]c%[1]s"
+# Valid: intersected type compatibility
+msgid  "abc%c"
+msgstr "xyz%[1]c%[1]q"
+# Invalid: intersected type incompatibility
+msgid  "abc%c"
+msgstr "xyz%[1]c%[1]e"
+# Invalid: intersected type incompatibility
+msgid  "abc%e"
+msgstr "xyz%[1]c%[1]e"
+# Invalid: intersected type incompatibility
+msgid  "abc%c"
+msgstr "xyz%[1]c%[1]O"
+# Invalid: intersected type incompatibility
+msgid  "abc%O"
+msgstr "xyz%[1]c%[1]O"
+# Invalid: intersected type incompatibility
+msgid  "abc%c"
+msgstr "xyz%[1]c%[1]d"
+# Invalid: intersected type incompatibility
+msgid  "abc%d"
+msgstr "xyz%[1]c%[1]d"
+# Invalid: intersected type incompatibility
+msgid  "abc%c"
+msgstr "xyz%[1]c%[1]b"
+# Invalid: intersected type incompatibility
+msgid  "abc%b"
+msgstr "xyz%[1]c%[1]b"
+# Invalid: intersected type incompatibility
+msgid  "abc%c"
+msgstr "xyz%[1]c%[1]x"
+# Invalid: intersected type incompatibility
+msgid  "abc%x"
+msgstr "xyz%[1]c%[1]x"
+# Valid: intersected type compatibility
+msgid  "abc%s"
+msgstr "xyz%[1]s%[1]q"
+# Invalid: intersected type incompatibility
+msgid  "abc%s"
+msgstr "xyz%[1]s%[1]e"
+# Invalid: intersected type incompatibility
+msgid  "abc%e"
+msgstr "xyz%[1]s%[1]e"
+# Invalid: intersected type incompatibility
+msgid  "abc%s"
+msgstr "xyz%[1]s%[1]O"
+# Invalid: intersected type incompatibility
+msgid  "abc%O"
+msgstr "xyz%[1]s%[1]O"
+# Invalid: intersected type incompatibility
+msgid  "abc%s"
+msgstr "xyz%[1]s%[1]d"
+# Invalid: intersected type incompatibility
+msgid  "abc%d"
+msgstr "xyz%[1]s%[1]d"
+# Invalid: intersected type incompatibility
+msgid  "abc%s"
+msgstr "xyz%[1]s%[1]b"
+# Invalid: intersected type incompatibility
+msgid  "abc%b"
+msgstr "xyz%[1]s%[1]b"
+# Valid: intersected type compatibility
+msgid  "abc%s"
+msgstr "xyz%[1]s%[1]x"
+# Invalid: intersected type incompatibility
+msgid  "abc%q"
+msgstr "xyz%[1]q%[1]e"
+# Invalid: intersected type incompatibility
+msgid  "abc%e"
+msgstr "xyz%[1]q%[1]e"
+# Invalid: intersected type incompatibility
+msgid  "abc%q"
+msgstr "xyz%[1]q%[1]O"
+# Invalid: intersected type incompatibility
+msgid  "abc%O"
+msgstr "xyz%[1]q%[1]O"
+# Invalid: intersected type incompatibility
+msgid  "abc%q"
+msgstr "xyz%[1]q%[1]d"
+# Invalid: intersected type incompatibility
+msgid  "abc%d"
+msgstr "xyz%[1]q%[1]d"
+# Invalid: intersected type incompatibility
+msgid  "abc%q"
+msgstr "xyz%[1]q%[1]b"
+# Invalid: intersected type incompatibility
+msgid  "abc%b"
+msgstr "xyz%[1]q%[1]b"
+# Valid: intersected type compatibility
+msgid  "abc%s"
+msgstr "xyz%[1]q%[1]x"
+# Invalid: intersected type incompatibility
+msgid  "abc%e"
+msgstr "xyz%[1]e%[1]O"
+# Invalid: intersected type incompatibility
+msgid  "abc%O"
+msgstr "xyz%[1]e%[1]O"
+# Invalid: intersected type incompatibility
+msgid  "abc%e"
+msgstr "xyz%[1]e%[1]d"
+# Invalid: intersected type incompatibility
+msgid  "abc%d"
+msgstr "xyz%[1]e%[1]d"
+# Valid: intersected type compatibility
+msgid  "abc%e"
+msgstr "xyz%[1]e%[1]b"
+# Valid: intersected type compatibility
+msgid  "abc%e"
+msgstr "xyz%[1]e%[1]x"
+# Valid: intersected type compatibility
+msgid  "abc%O"
+msgstr "xyz%[1]O%[1]d"
+# Valid: intersected type compatibility
+msgid  "abc%O"
+msgstr "xyz%[1]O%[1]b"
+# Valid: intersected type compatibility
+msgid  "abc%O"
+msgstr "xyz%[1]O%[1]x"
+# Valid: intersected type compatibility
+msgid  "abc%d"
+msgstr "xyz%[1]d%[1]b"
+# Valid: intersected type compatibility
+msgid  "abc%d"
+msgstr "xyz%[1]d%[1]x"
+# Valid: intersected type compatibility
+msgid  "abc%b"
+msgstr "xyz%[1]b%[1]x"
+# Valid: intersected type compatibility
+msgid  "abc%O"
+msgstr "xyz%[1]*[1]d"
+# Valid: intersected type compatibility
+msgid  "abc%O"
+msgstr "xyz%.[1]*[1]d"
+EOF
+
+: ${MSGFMT=msgfmt}
+n=0
+while read comment; do
+  read msgid_line
+  read msgstr_line
+  n=`expr $n + 1`
+  cat <<EOF > f-go-2-$n.po
+#, go-format
+${msgid_line}
+${msgstr_line}
+EOF
+  fail=
+  if echo "$comment" | grep 'Valid:' > /dev/null; then
+    if ${MSGFMT} --check-format -o f-go-2-$n.mo f-go-2-$n.po; then
+      :
+    else
+      fail=yes
+    fi
+  else
+    ${MSGFMT} --check-format -o f-go-2-$n.mo f-go-2-$n.po 2> /dev/null
+    if test $? = 1; then
+      :
+    else
+      fail=yes
+    fi
+  fi
+  if test -n "$fail"; then
+    echo "Format string checking error:" 1>&2
+    cat f-go-2-$n.po 1>&2
+    Exit 1
+  fi
+  rm -f f-go-2-$n.po f-go-2-$n.mo
+done < f-go-2.data
+
+Exit 0
diff --git a/gettext-tools/tests/xgettext-go-1 b/gettext-tools/tests/xgettext-go-1
new file mode 100755 (executable)
index 0000000..fd30c50
--- /dev/null
@@ -0,0 +1,64 @@
+#!/bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test Go support: github.com/leonelquinteros/gotext package,
+# with single-locale API.
+
+cat <<\EOF > xg-go-1.go
+package main
+
+import (
+       // Documentation: https://pkg.go.dev/fmt
+       "fmt"
+       // Documentation: https://pkg.go.dev/github.com/leonelquinteros/gotext
+       "github.com/leonelquinteros/gotext"
+       // Documentation: https://pkg.go.dev/os
+       "os"
+)
+
+// Returns the language in the form "ll_CC".
+// Alternatives:
+// - https://pkg.go.dev/github.com/Xuanwo/go-locale
+// - https://pkg.go.dev/github.com/jeandeaual/go-locale
+func getUserLanguage() string {
+       // Look at the POSIX environment variables.
+       for _, variable := range []string{"LC_ALL", "LC_MESSAGES", "LANG"} {
+               if value := os.Getenv(variable); value != "" {
+                       return gotext.SimplifiedLocale(value)
+               }
+       }
+       // The "C" locale is essentially the same as the en-US locale.
+       return "en_US"
+}
+
+func main () {
+       // Specify locale.
+       //language := "fr_FR"
+       language := getUserLanguage()
+
+       // Specify localedir, domain.
+       gotext.Configure("@localedir@", language, "hello")
+
+       fmt.Println(gotext.Get("Hello, world!"))
+       fmt.Println(gotext.Get("This program is running as process number %d.",
+                              os.Getpid()))
+}
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --omit-header --no-location -d xg-go-1.tmp xg-go-1.go || Exit 1
+LC_ALL=C tr -d '\r' < xg-go-1.tmp.po > xg-go-1.po || Exit 1
+
+cat <<\EOF > xg-go-1.ok
+msgid "Hello, world!"
+msgstr ""
+
+#, go-format
+msgid "This program is running as process number %d."
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-go-1.ok xg-go-1.po || Exit 1
+
+exit 0
diff --git a/gettext-tools/tests/xgettext-go-10 b/gettext-tools/tests/xgettext-go-10
new file mode 100755 (executable)
index 0000000..abcca79
--- /dev/null
@@ -0,0 +1,152 @@
+#!/bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test Go support: propagation of 'go-format'.
+
+cat <<\EOF > xg-go-10.go
+package main
+
+import (
+       "fmt"
+       "github.com/leonelquinteros/gotext"
+)
+
+func foo () string {
+       return "x"
+}
+
+func main () {
+       // Specify localedir, domain.
+       gotext.Configure(".", "fr_FR", "hello")
+
+       gotext.Get("Hello10")
+       (gotext.Get("Hello11"))
+       ((gotext.Get("Hello12")))
+       gotext.Get(gotext.Get("Hello13"))
+       (gotext.Get(gotext.Get("Hello14")))
+       ((gotext.Get(gotext.Get("Hello15"))))
+       gotext.Get((gotext.Get("Hello16")))
+       gotext.Get(((gotext.Get("Hello17"))))
+       gotext.Get(foo(), gotext.Get("Hello18"))
+
+       fmt.Sprintf(gotext.Get("Hello20"))
+       fmt.Sprintf((gotext.Get("Hello21")))
+       fmt.Sprintf(((gotext.Get("Hello22"))))
+       fmt.Sprintf(gotext.Get(gotext.Get("Hello23")))
+       fmt.Sprintf((gotext.Get(gotext.Get("Hello24"))))
+       fmt.Sprintf(((gotext.Get(gotext.Get("Hello25")))))
+       fmt.Sprintf(gotext.Get((gotext.Get("Hello26"))))
+       fmt.Sprintf(gotext.Get(((gotext.Get("Hello27")))))
+       fmt.Sprintf(gotext.Get(foo(), gotext.Get("Hello28")))
+
+       fmt.Println(gotext.Get("Hello30"))
+       fmt.Println((gotext.Get("Hello31")))
+       fmt.Println(((gotext.Get("Hello32"))))
+       fmt.Println(gotext.Get(gotext.Get("Hello33")))
+       fmt.Println((gotext.Get(gotext.Get("Hello34"))))
+       fmt.Println(((gotext.Get(gotext.Get("Hello35")))))
+       fmt.Println(gotext.Get((gotext.Get("Hello36"))))
+       fmt.Println(gotext.Get(((gotext.Get("Hello37")))))
+       fmt.Println(gotext.Get(foo(), gotext.Get("Hello38")))
+}
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --omit-header --no-location -d xg-go-10.tmp xg-go-10.go || Exit 1
+LC_ALL=C tr -d '\r' < xg-go-10.tmp.po > xg-go-10.po || Exit 1
+
+cat <<\EOF > xg-go-10.ok
+msgid "Hello10"
+msgstr ""
+
+msgid "Hello11"
+msgstr ""
+
+msgid "Hello12"
+msgstr ""
+
+msgid "Hello13"
+msgstr ""
+
+msgid "Hello14"
+msgstr ""
+
+msgid "Hello15"
+msgstr ""
+
+msgid "Hello16"
+msgstr ""
+
+msgid "Hello17"
+msgstr ""
+
+msgid "Hello18"
+msgstr ""
+
+#, go-format
+msgid "Hello20"
+msgstr ""
+
+#, go-format
+msgid "Hello21"
+msgstr ""
+
+#, go-format
+msgid "Hello22"
+msgstr ""
+
+#, go-format
+msgid "Hello23"
+msgstr ""
+
+#, go-format
+msgid "Hello24"
+msgstr ""
+
+#, go-format
+msgid "Hello25"
+msgstr ""
+
+#, go-format
+msgid "Hello26"
+msgstr ""
+
+#, go-format
+msgid "Hello27"
+msgstr ""
+
+msgid "Hello28"
+msgstr ""
+
+msgid "Hello30"
+msgstr ""
+
+msgid "Hello31"
+msgstr ""
+
+msgid "Hello32"
+msgstr ""
+
+msgid "Hello33"
+msgstr ""
+
+msgid "Hello34"
+msgstr ""
+
+msgid "Hello35"
+msgstr ""
+
+msgid "Hello36"
+msgstr ""
+
+msgid "Hello37"
+msgstr ""
+
+msgid "Hello38"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-go-10.ok xg-go-10.po || Exit 1
+
+exit 0
diff --git a/gettext-tools/tests/xgettext-go-11 b/gettext-tools/tests/xgettext-go-11
new file mode 100755 (executable)
index 0000000..703d601
--- /dev/null
@@ -0,0 +1,42 @@
+#!/bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test Go support: import statements and package shortnames.
+
+cat <<\EOF > xg-go-11.go
+package main
+
+import "fmt"
+import foo "github.com/leonelquinteros/gotext"
+
+type S struct {
+}
+
+func (S) Get (s string) string {
+       return s
+}
+
+var gotext = new (S)
+
+func main () {
+       // Specify localedir, domain.
+       foo.Configure(".", "fr_FR", "hello")
+
+       fmt.Println(gotext.Get("Test 1"))
+       fmt.Println(foo.Get("Test 2"))
+}
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --omit-header --no-location -d xg-go-11.tmp xg-go-11.go || Exit 1
+LC_ALL=C tr -d '\r' < xg-go-11.tmp.po > xg-go-11.po || Exit 1
+
+cat <<\EOF > xg-go-11.ok
+msgid "Test 2"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-go-11.ok xg-go-11.po || Exit 1
+
+exit 0
diff --git a/gettext-tools/tests/xgettext-go-12 b/gettext-tools/tests/xgettext-go-12
new file mode 100755 (executable)
index 0000000..70e7649
--- /dev/null
@@ -0,0 +1,73 @@
+#!/bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test Go support: global type definitions.
+# Note: For this test case, 'xgotext' fails to extract "Test 1".
+
+cat <<\EOF > xg-go-12.go
+package main
+
+import (
+       "fmt"
+       "strconv"
+       "github.com/leonelquinteros/gotext"
+)
+
+var NL1 = func () *gotext.Locale {
+       return gotext.NewLocale(".", "fr_FR")
+}
+
+
+type NotLocale2 struct {
+       a int
+}
+
+func (nl NotLocale2) Get (s string) string {
+       return s + strconv.Itoa(nl.a)
+}
+
+var NL2 = func () NotLocale2 {
+       return NotLocale2{}
+}
+
+
+type NotLocale3 struct {
+       b int
+       Get func (s string) string
+}
+
+var NL3 = func () NotLocale3 {
+       return NotLocale3{}
+}
+
+
+type NotLocale4 interface {
+       Get (s string) string
+}
+
+var NL4 = func () NotLocale4 {
+       return * new (NotLocale4)
+}
+
+
+func main () {
+       fmt.Println(NL1().Get("Test 1"))
+       fmt.Println(NL2().Get("Test 2"))
+       fmt.Println(NL3().Get("Test 3"))
+       fmt.Println(NL4().Get("Test 4"))
+}
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --omit-header --no-location -d xg-go-12.tmp xg-go-12.go || Exit 1
+LC_ALL=C tr -d '\r' < xg-go-12.tmp.po > xg-go-12.po || Exit 1
+
+cat <<\EOF > xg-go-12.ok
+msgid "Test 1"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-go-12.ok xg-go-12.po || Exit 1
+
+exit 0
diff --git a/gettext-tools/tests/xgettext-go-13 b/gettext-tools/tests/xgettext-go-13
new file mode 100755 (executable)
index 0000000..2156400
--- /dev/null
@@ -0,0 +1,63 @@
+#!/bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test Go support: global variable declarations.
+
+cat <<\EOF > xg-go-13.go
+package main
+
+import (
+       "fmt"
+       "github.com/leonelquinteros/gotext"
+)
+
+var L1 = gotext.NewLocale(".", "fr_FR")
+
+
+type NotLocale2 struct {
+       b int
+       Get func (s string) string
+}
+
+var L2 = NotLocale2{}
+
+
+var L3, L4 = NotLocale2{}, gotext.NewLocale(".", "fr_FR")
+
+
+var NL = func () (NotLocale2, *gotext.Locale) {
+       return NotLocale2{}, gotext.NewLocale(".", "fr_FR")
+}
+
+var L5, L6 = NL()
+
+
+func main () {
+       fmt.Println(L1.Get("Test 1"))
+       fmt.Println(L2.Get("Test 2"))
+       fmt.Println(L3.Get("Test 3"))
+       fmt.Println(L4.Get("Test 4"))
+       fmt.Println(L5.Get("Test 5"))
+       fmt.Println(L6.Get("Test 6"))
+}
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --omit-header --no-location -d xg-go-13.tmp xg-go-13.go || Exit 1
+LC_ALL=C tr -d '\r' < xg-go-13.tmp.po > xg-go-13.po || Exit 1
+
+cat <<\EOF > xg-go-13.ok
+msgid "Test 1"
+msgstr ""
+
+msgid "Test 4"
+msgstr ""
+
+msgid "Test 6"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-go-13.ok xg-go-13.po || Exit 1
+
+exit 0
diff --git a/gettext-tools/tests/xgettext-go-14 b/gettext-tools/tests/xgettext-go-14
new file mode 100755 (executable)
index 0000000..b2ea17d
--- /dev/null
@@ -0,0 +1,61 @@
+#!/bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test Go support: global function declarations.
+
+cat <<\EOF > xg-go-14.go
+package main
+
+import (
+       "fmt"
+       . "github.com/leonelquinteros/gotext"
+)
+
+func NL1 () *Locale {
+       return NewLocale(".", "fr_FR")
+}
+
+
+type NotLocale2 struct {
+       b int
+       Get func (s string) string
+}
+
+func NL2 () NotLocale2 {
+       return NotLocale2{}
+}
+
+func NL3 () (*Locale, NotLocale2) {
+       return NL1(), NL2()
+}
+
+func NL4 () (NotLocale2, Locale) {
+       return NL2(), *NL1()
+}
+
+func main () {
+       fmt.Println(NL1().Get("Test 1"))
+       fmt.Println(NL2().Get("Test 2"))
+       _, v3 := NL3()
+       fmt.Println(v3.Get("Test 3"))
+       _, v4 := NL4()
+       fmt.Println(v4.Get("Test 4"))
+}
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --omit-header --no-location -d xg-go-14.tmp xg-go-14.go || Exit 1
+LC_ALL=C tr -d '\r' < xg-go-14.tmp.po > xg-go-14.po || Exit 1
+
+cat <<\EOF > xg-go-14.ok
+msgid "Test 1"
+msgstr ""
+
+msgid "Test 4"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-go-14.ok xg-go-14.po || Exit 1
+
+exit 0
diff --git a/gettext-tools/tests/xgettext-go-15 b/gettext-tools/tests/xgettext-go-15
new file mode 100755 (executable)
index 0000000..bf939eb
--- /dev/null
@@ -0,0 +1,46 @@
+#!/bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test Go support: local type definitions.
+
+cat <<\EOF > xg-go-15.go
+package main
+
+import (
+       "fmt"
+       . "github.com/leonelquinteros/gotext"
+)
+
+type L = Locale
+
+func main () {
+       fmt.Println(new (Locale).Get("Test 1"))
+       fmt.Println(new (L).Get("Test 2"))
+       type L struct {
+               b int
+               Get func (s string) string
+       }
+       fmt.Println(new (L).Get("Test 3"))
+       fmt.Println(new (Locale).Get("Test 4"))
+}
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --omit-header --no-location -d xg-go-15.tmp xg-go-15.go || Exit 1
+LC_ALL=C tr -d '\r' < xg-go-15.tmp.po > xg-go-15.po || Exit 1
+
+cat <<\EOF > xg-go-15.ok
+msgid "Test 1"
+msgstr ""
+
+msgid "Test 2"
+msgstr ""
+
+msgid "Test 4"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-go-15.ok xg-go-15.po || Exit 1
+
+exit 0
diff --git a/gettext-tools/tests/xgettext-go-16 b/gettext-tools/tests/xgettext-go-16
new file mode 100755 (executable)
index 0000000..9a7ed8a
--- /dev/null
@@ -0,0 +1,106 @@
+#!/bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test Go support: local variable declarations.
+
+cat <<\EOF > xg-go-16.go
+package main
+
+import (
+       "fmt"
+       "github.com/leonelquinteros/gotext"
+)
+
+var L1 = gotext.NewLocale(".", "fr_FR")
+var L2 = L1
+var L3 = L1
+
+type NotLocale2 struct {
+       b int
+       Get func (s string) string
+}
+
+var L4 NotLocale2
+var L5 NotLocale2
+
+var NL = func () (*gotext.Locale, NotLocale2) {
+       return gotext.NewLocale(".", "fr_FR"), NotLocale2{}
+}
+
+func foo (a int, L gotext.Locale) {
+       fmt.Println(L.Get("Test 01"))
+       fmt.Println(L1.Get("Test 02"))
+       fmt.Println(L2.Get("Test 03"))
+       fmt.Println(L3.Get("Test 04"))
+
+       var L2 = NotLocale2{}
+       fmt.Println(L2.Get("Test 05"))
+
+       L3 := NotLocale2{}
+       fmt.Println(L3.Get("Test 06"))
+
+       var L4 = gotext.NewLocale(".", "fr_FR")
+       fmt.Println(L4.Get("Test 07"))
+
+       L5 := gotext.NewLocale(".", "fr_FR")
+       fmt.Println(L5.Get("Test 08"))
+
+       var L6, L7 = NotLocale2{}, gotext.NewLocale(".", "fr_FR")
+       fmt.Println(L6.Get("Test 09"))
+       fmt.Println(L7.Get("Test 10"))
+
+       {
+               var NL = func () (NotLocale2, *gotext.Locale) {
+                       return NotLocale2{}, gotext.NewLocale(".", "fr_FR")
+               }
+               var L8, L9 = NL()
+               fmt.Println(L8.Get("Test 11"))
+               fmt.Println(L9.Get("Test 12"))
+       }
+       var L8, L9 = NL()
+       fmt.Println(L8.Get("Test 13"))
+       fmt.Println(L9.Get("Test 14"))
+}
+
+func main () {
+       foo(2, *L1)
+}
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --omit-header --no-location -d xg-go-16.tmp xg-go-16.go || Exit 1
+LC_ALL=C tr -d '\r' < xg-go-16.tmp.po > xg-go-16.po || Exit 1
+
+cat <<\EOF > xg-go-16.ok
+msgid "Test 01"
+msgstr ""
+
+msgid "Test 02"
+msgstr ""
+
+msgid "Test 03"
+msgstr ""
+
+msgid "Test 04"
+msgstr ""
+
+msgid "Test 07"
+msgstr ""
+
+msgid "Test 08"
+msgstr ""
+
+msgid "Test 10"
+msgstr ""
+
+msgid "Test 12"
+msgstr ""
+
+msgid "Test 13"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-go-16.ok xg-go-16.po || Exit 1
+
+exit 0
diff --git a/gettext-tools/tests/xgettext-go-17 b/gettext-tools/tests/xgettext-go-17
new file mode 100755 (executable)
index 0000000..fc1bab0
--- /dev/null
@@ -0,0 +1,71 @@
+#!/bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test Go support: local function declarations.
+
+cat <<\EOF > xg-go-17.go
+package main
+
+import (
+       "fmt"
+       "github.com/leonelquinteros/gotext"
+)
+
+func main () {
+
+       var NL1 = func () *gotext.Locale {
+               return gotext.NewLocale(".", "fr_FR")
+       }
+
+
+       type NotLocale2 struct {
+               l *gotext.Locale
+       }
+
+       var NL2 = func () *NotLocale2 {
+               return new (NotLocale2)
+       }
+
+
+       type NotLocale3 struct {
+               b int
+               Get func (s string) string
+       }
+
+       var NL3 = func () NotLocale3 {
+               return NotLocale3{}
+       }
+
+
+       type NotLocale4 interface {
+               Get (s string) string
+       }
+
+       var NL4 = func () NotLocale4 {
+               return * new (NotLocale4)
+       }
+
+
+       fmt.Println(NL1().Get("Test 1"))
+       fmt.Println(NL2().l.Get("Test 2"))
+       fmt.Println(NL3().Get("Test 3"))
+       fmt.Println(NL4().Get("Test 4"))
+}
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --omit-header --no-location -d xg-go-17.tmp xg-go-17.go || Exit 1
+LC_ALL=C tr -d '\r' < xg-go-17.tmp.po > xg-go-17.po || Exit 1
+
+cat <<\EOF > xg-go-17.ok
+msgid "Test 1"
+msgstr ""
+
+msgid "Test 2"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-go-17.ok xg-go-17.po || Exit 1
+
+exit 0
diff --git a/gettext-tools/tests/xgettext-go-18 b/gettext-tools/tests/xgettext-go-18
new file mode 100755 (executable)
index 0000000..5f975de
--- /dev/null
@@ -0,0 +1,89 @@
+#!/bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test Go support: types of expressions.
+
+cat <<\EOF > xg-go-18.go
+package main
+
+import (
+       "fmt"
+       "strconv"
+       "github.com/leonelquinteros/gotext"
+)
+
+var NL1 = func () *gotext.Locale {
+       return gotext.NewLocale(".", "fr_FR")
+} ()
+
+
+type NotLocale2 struct {
+       a int
+       l *gotext.Locale
+}
+
+func (nl NotLocale2) Get (s string) string {
+       return s + strconv.Itoa(nl.a)
+}
+
+var NL2 = func () NotLocale2 {
+       return NotLocale2{}
+} ()
+
+
+func main () {
+       x1, x2 := NL1, NL2
+       fmt.Println(x1.Get("Test 1"))
+       fmt.Println(x2.Get("Test 2"))
+
+       x3 := *NL1
+       fmt.Println(x3.Get("Test 3"))
+
+       x4 := &NL2
+       fmt.Println(x4.Get("Test 4"))
+
+       x5 := NL2.l
+       fmt.Println(x5.Get("Test 5"))
+
+       var x6 []NotLocale2
+       fmt.Println(x6[2].Get("Test 6a"))
+       fmt.Println(x6[2].l.Get("Test 6b"))
+
+       var x7 []NotLocale2
+       fmt.Println(x7[1:5][2].Get("Test 7a"))
+       fmt.Println(x7[1:5][2].l.Get("Test 7b"))
+
+       var x8 map[string]NotLocale2
+       fmt.Println(x8["+"].Get("Test 8a"))
+       fmt.Println(x8["+"].l.Get("Test 8b"))
+}
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --omit-header --no-location -d xg-go-18.tmp xg-go-18.go || Exit 1
+LC_ALL=C tr -d '\r' < xg-go-18.tmp.po > xg-go-18.po || Exit 1
+
+cat <<\EOF > xg-go-18.ok
+msgid "Test 1"
+msgstr ""
+
+msgid "Test 3"
+msgstr ""
+
+msgid "Test 5"
+msgstr ""
+
+msgid "Test 6b"
+msgstr ""
+
+msgid "Test 7b"
+msgstr ""
+
+msgid "Test 8b"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-go-18.ok xg-go-18.po || Exit 1
+
+exit 0
diff --git a/gettext-tools/tests/xgettext-go-2 b/gettext-tools/tests/xgettext-go-2
new file mode 100755 (executable)
index 0000000..e0b3662
--- /dev/null
@@ -0,0 +1,66 @@
+#!/bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test Go support: github.com/leonelquinteros/gotext package,
+# with multi-locale API, for a standalone application.
+
+cat <<\EOF > xg-go-2.go
+package main
+
+import (
+       // Documentation: https://pkg.go.dev/fmt
+       "fmt"
+       // Documentation: https://pkg.go.dev/github.com/leonelquinteros/gotext
+       "github.com/leonelquinteros/gotext"
+       // Documentation: https://pkg.go.dev/os
+       "os"
+)
+
+// Returns the language in the form "ll_CC".
+// Alternatives:
+// - https://pkg.go.dev/github.com/Xuanwo/go-locale
+// - https://pkg.go.dev/github.com/jeandeaual/go-locale
+func getUserLanguage() string {
+       // Look at the POSIX environment variables.
+       for _, variable := range []string{"LC_ALL", "LC_MESSAGES", "LANG"} {
+               if value := os.Getenv(variable); value != "" {
+                       return gotext.SimplifiedLocale(value)
+               }
+       }
+       // The "C" locale is essentially the same as the en-US locale.
+       return "en_US"
+}
+
+func main () {
+       // Specify locale.
+       //language := "fr_FR"
+       language := getUserLanguage()
+
+       // Specify localedir.
+       localizer := gotext.NewLocale("@localedir@", language)
+       // Specify domain.
+       localizer.AddDomain("hello")
+
+       fmt.Println(localizer.Get("Hello, world!"))
+       fmt.Println(localizer.Get("This program is running as process number %d.",
+                                 os.Getpid()))
+}
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --omit-header --no-location -d xg-go-2.tmp xg-go-2.go || Exit 1
+LC_ALL=C tr -d '\r' < xg-go-2.tmp.po > xg-go-2.po || Exit 1
+
+cat <<\EOF > xg-go-2.ok
+msgid "Hello, world!"
+msgstr ""
+
+#, go-format
+msgid "This program is running as process number %d."
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-go-2.ok xg-go-2.po || Exit 1
+
+exit 0
diff --git a/gettext-tools/tests/xgettext-go-3 b/gettext-tools/tests/xgettext-go-3
new file mode 100755 (executable)
index 0000000..48db28a
--- /dev/null
@@ -0,0 +1,102 @@
+#!/bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test Go support: github.com/leonelquinteros/gotext package,
+# with multi-locale API, for a web server.
+
+cat <<\EOF > xg-go-3.go
+package main
+
+import (
+       // Documentation: https://pkg.go.dev/fmt
+       "fmt"
+       // Documentation: https://pkg.go.dev/github.com/leonelquinteros/gotext
+       "github.com/leonelquinteros/gotext"
+       // Other Go packages
+       "context"
+       "log"
+       "net/http"
+       "strings"
+)
+
+var localizer_table map[string]*gotext.Locale
+
+// key under which to store the language in the context
+const langKey string = "userLanguage"
+
+// Middleware to extract language from request and store in context
+func languageMiddleware(next http.Handler) http.Handler {
+       return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+               acceptLang := r.Header.Get("Accept-Language") // Extract language from header
+               // Spec: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language
+               lang := "en" // Default to English
+               if acceptLang != "" {
+                       for _, l := range strings.Split(acceptLang, ",") {
+                               l = strings.TrimSpace(strings.Split(l, ";")[0]) // Remove quality values
+                               if _, exists := localizer_table[l]; exists {
+                                       lang = l
+                                       break
+                               }
+                       }
+               }
+
+               // Store language in the context
+               ctx := context.WithValue(r.Context(), langKey, lang)
+
+               // Pass request with new context to the next handler
+               next.ServeHTTP(w, r.WithContext(ctx))
+       })
+}
+
+// Handler that retrieves the per-goroutine language from the context
+func handler(w http.ResponseWriter, r *http.Request) {
+       // Retrieve language from the context
+       lang, _ := r.Context().Value(langKey).(string)
+
+       fmt.Fprintf(w, "Detected language: %s\n", lang)
+
+       localizer := localizer_table[lang]
+
+       fmt.Fprintln(w, localizer.Get("Hello world!"))
+       fmt.Fprintln(w, localizer.Get("Hello %s", "Dolly"))
+}
+
+func main() {
+       // Preload all the existing translations into the localizer_table.
+       // This leads to faster response times than allocating the localizer
+       // lazily, for each HTTP request.
+       localizer_table = make(map[string]*gotext.Locale)
+       for _, language := range []string { "en_US", "de_DE", "fr_FR"} {
+               // Specify localedir, locale.
+               localizer := gotext.NewLocale("@localedir@", language)
+               // Specify domain.
+               localizer.AddDomain("hello")
+               localizer_table[strings.Split(language, "_")[0]] = localizer
+       }
+
+       mux := http.NewServeMux()
+       mux.Handle("/", languageMiddleware(http.HandlerFunc(handler)))
+
+       port := 8080
+       fmt.Printf("Server listening on port %d\n", port)
+       log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), mux))
+}
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --omit-header --no-location -d xg-go-3.tmp xg-go-3.go || Exit 1
+LC_ALL=C tr -d '\r' < xg-go-3.tmp.po > xg-go-3.po || Exit 1
+
+cat <<\EOF > xg-go-3.ok
+msgid "Hello world!"
+msgstr ""
+
+#, go-format
+msgid "Hello %s"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-go-3.ok xg-go-3.po || Exit 1
+
+exit 0
diff --git a/gettext-tools/tests/xgettext-go-4 b/gettext-tools/tests/xgettext-go-4
new file mode 100755 (executable)
index 0000000..aea8dd7
--- /dev/null
@@ -0,0 +1,52 @@
+#!/bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test Go support: github.com/gosexy/gettext package,
+# with single-locale API.
+
+cat <<\EOF > xg-go-4.go
+package main
+
+import (
+       // Documentation: https://pkg.go.dev/fmt
+       "fmt"
+       // Documentation: https://pkg.go.dev/github.com/gosexy/gettext
+       "github.com/gosexy/gettext"
+       // Documentation: https://pkg.go.dev/os
+       "os"
+)
+
+func main () {
+       // Specify domain, localedir.
+       domain := "hello"
+       gettext.BindTextdomain(domain, "@localedir@")
+       gettext.Textdomain(domain)
+
+       // Specify locale.
+       //locale := "fr_FR.UTF-8"
+       locale := "" // looks at the POSIX environment variables
+       gettext.SetLocale(gettext.LcAll, locale)
+
+       fmt.Println(gettext.Gettext("Hello, world!"))
+       fmt.Println(fmt.Sprintf(gettext.Gettext("This program is running as process number %d."),
+                               os.Getpid()))
+}
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --omit-header --no-location -d xg-go-4.tmp xg-go-4.go || Exit 1
+LC_ALL=C tr -d '\r' < xg-go-4.tmp.po > xg-go-4.po || Exit 1
+
+cat <<\EOF > xg-go-4.ok
+msgid "Hello, world!"
+msgstr ""
+
+#, go-format
+msgid "This program is running as process number %d."
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-go-4.ok xg-go-4.po || Exit 1
+
+exit 0
diff --git a/gettext-tools/tests/xgettext-go-5 b/gettext-tools/tests/xgettext-go-5
new file mode 100755 (executable)
index 0000000..69ff4e5
--- /dev/null
@@ -0,0 +1,68 @@
+#!/bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test Go support: github.com/snapcore/go-gettext package,
+# with API suitable for single-locale and multi-locale cases.
+
+cat <<\EOF > xg-go-5.go
+package main
+
+import (
+       // Documentation: https://pkg.go.dev/fmt
+       "fmt"
+       // Documentation: https://pkg.go.dev/github.com/snapcore/go-gettext
+       "github.com/snapcore/go-gettext"
+       // Documentation: https://pkg.go.dev/os
+       "os"
+)
+
+// Alternatives:
+// - gettext.getUserLanguages()
+// - https://pkg.go.dev/github.com/Xuanwo/go-locale
+// - https://pkg.go.dev/github.com/jeandeaual/go-locale
+func getUserLanguage() string {
+       // Look at the POSIX environment variables.
+       for _, variable := range []string{"LC_ALL", "LC_MESSAGES", "LANG"} {
+               if value := os.Getenv(variable); value != "" {
+                       return value
+               }
+       }
+       // The "C" locale is essentially the same as the en-US locale.
+       return "en-US"
+}
+
+func main () {
+       // Specify locale.
+       //locale := "fr_FR"
+       locale := getUserLanguage()
+
+       // Specify domain, localedir.
+       domain := &gettext.TextDomain{
+               Name: "hello",
+               LocaleDir: "@localedir@"
+       }
+       gettext := domain.Locale(locale)
+
+       fmt.Println(gettext.Gettext("Hello, world!"))
+       fmt.Println(fmt.Sprintf(gettext.Gettext("This program is running as process number %d."),
+                               os.Getpid()))
+}
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --omit-header --no-location -d xg-go-5.tmp xg-go-5.go || Exit 1
+LC_ALL=C tr -d '\r' < xg-go-5.tmp.po > xg-go-5.po || Exit 1
+
+cat <<\EOF > xg-go-5.ok
+msgid "Hello, world!"
+msgstr ""
+
+#, go-format
+msgid "This program is running as process number %d."
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-go-5.ok xg-go-5.po || Exit 1
+
+exit 0
diff --git a/gettext-tools/tests/xgettext-go-6 b/gettext-tools/tests/xgettext-go-6
new file mode 100755 (executable)
index 0000000..073d16f
--- /dev/null
@@ -0,0 +1,92 @@
+#!/bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test Go support: Simple things.
+
+cat <<\EOF > xg-go-6.go
+package main
+
+import (
+       "fmt"
+       "github.com/leonelquinteros/gotext"
+)
+
+func main () {
+       // Specify localedir, domain.
+       gotext.Configure(".", "fr_FR", "hello")
+
+       // C++ style comment
+       fmt.Println(gotext.Get("Test 1"))
+       /* C style comment */
+       fmt.Println(gotext.Get("Test 2"))
+       // Raw string literals
+       fmt.Println(gotext.Get(`Test 3`))
+       fmt.Println(gotext.Get(`first line
+second line
+third line`))
+       // String literal concatenation
+       fmt.Println(gotext.Get(`Test 4` + "711"))
+       // Empty string
+       fmt.Println(gotext.Get(""))
+}
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} -c -d xg-go-6.tmp xg-go-6.go || Exit 1
+LC_ALL=C tr -d '\r' < xg-go-6.tmp.po > xg-go-6.po || Exit 1
+
+sed -e '/POT-Creation-Date/d' < xg-go-6.po > xg-go-6.pot
+
+cat <<\EOF > xg-go-6.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.
+#
+#. Empty string
+#: xg-go-6.go:24
+#, 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 <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#. C++ style comment
+#: xg-go-6.go:13
+msgid "Test 1"
+msgstr ""
+
+#. C style comment
+#: xg-go-6.go:15
+msgid "Test 2"
+msgstr ""
+
+#. Raw string literals
+#: xg-go-6.go:17
+msgid "Test 3"
+msgstr ""
+
+#: xg-go-6.go:18
+msgid ""
+"first line\n"
+"second line\n"
+"third line"
+msgstr ""
+
+#. String literal concatenation
+#: xg-go-6.go:22
+msgid "Test 4711"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-go-6.ok xg-go-6.pot || Exit 1
+
+exit 0
diff --git a/gettext-tools/tests/xgettext-go-7 b/gettext-tools/tests/xgettext-go-7
new file mode 100755 (executable)
index 0000000..aadd1ef
--- /dev/null
@@ -0,0 +1,154 @@
+#!/bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test Go support: UTF-8 characters and Unicode escapes.
+
+cat <<\EOF > xg-go-7.go
+package main
+
+import (
+       "fmt"
+       "github.com/leonelquinteros/gotext"
+)
+
+func main () {
+       // Specify localedir, domain.
+       gotext.Configure(".", "fr_FR", "hello")
+
+       fmt.Println(gotext.Get("Russian (Русский): Здравствуйте"))
+       fmt.Println(gotext.Get("Vietnamese (Tiếng Việt): Chào bạn"))
+       fmt.Println(gotext.Get("Japanese (日本語): こんにちは"))
+       fmt.Println(gotext.Get("Thai (ภาษาไทย): สวัสดีครับ"))
+       fmt.Println(gotext.Get("Script: 𝒞"))
+       fmt.Println(gotext.Get("Russian (\u0420\u0443\u0441\u0441\u043a\u0438\u0439): \u0417\u0434\u0440\u0430\u0432\u0441\u0442\u0432\u0443\u0439\u0442\u0435"))
+       fmt.Println(gotext.Get("Vietnamese (Ti\u1ebfng Vi\u1ec7t): Ch\u00e0o b\u1ea1n"))
+       fmt.Println(gotext.Get("Japanese (\u65e5\u672c\u8a9e): \u3053\u3093\u306b\u3061\u306f"))
+       fmt.Println(gotext.Get("Thai (\u0e20\u0e32\u0e29\u0e32\u0e44\u0e17\u0e22): \u0e2a\u0e27\u0e31\u0e2a\u0e14\u0e35\u0e04\u0e23\u0e31\u0e1a"))
+       fmt.Println(gotext.Get("Script: \U0001d49e"))
+       // And now a comment with Русский and 日本語 and Unicode escapes: B\u00f6se B\u00fcbchen
+       fmt.Println(gotext.Get("This string has a multilingual comment"))
+       // Unicode identifiers.
+       var あ string = ""
+       𐀀 := ""
+       fmt.Println(あ + 𐀀)
+       fmt.Println(gotext.Get("Embedded\nnewline"))
+       // Two backslashes (unlike in Java, where this is just one backslash).
+       fmt.Println(gotext.Get("\u005c\u005c"));
+       // A 6-character string (unlike in Java, where this is just one backslash).
+       fmt.Println(gotext.Get("\\u005c"));
+       // A single backslash.
+       fmt.Println(gotext.Get("\\"));
+       // Escape sequences in strings.
+       fmt.Println(gotext.Get("t -> \t, n -> \n, dquote -> \" ..."));
+       // Hex escapes are recognized.
+       fmt.Println(gotext.Get("bel: \x07\n"));
+       fmt.Println(gotext.Get // Recognized despite comments
+               ( /* Even across multiline
+comment! */ "this is a single long line"));
+       // In raw string literals, no escape sequences are recognized.
+       fmt.Println(gotext.Get(`raw 2 \u005c \\ \t \n \"`));
+       // Rune literals are not extracted.
+       fmt.Println(gotext.Get('x'));
+}
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} -c -d xg-go-7.tmp xg-go-7.go || Exit 1
+LC_ALL=C tr -d '\r' < xg-go-7.tmp.po > xg-go-7.po || Exit 1
+
+sed -e '/POT-Creation-Date/d' < xg-go-7.po > xg-go-7.pot
+
+cat <<\EOF > xg-go-7.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 <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: xg-go-7.go:12 xg-go-7.go:17
+msgid "Russian (Русский): Здравствуйте"
+msgstr ""
+
+#: xg-go-7.go:13 xg-go-7.go:18
+msgid "Vietnamese (Tiếng Việt): Chào bạn"
+msgstr ""
+
+#: xg-go-7.go:14 xg-go-7.go:19
+msgid "Japanese (日本語): こんにちは"
+msgstr ""
+
+#: xg-go-7.go:15 xg-go-7.go:20
+msgid "Thai (ภาษาไทย): สวัสดีครับ"
+msgstr ""
+
+#: xg-go-7.go:16 xg-go-7.go:21
+msgid "Script: 𝒞"
+msgstr ""
+
+#. And now a comment with Русский and 日本語 and Unicode escapes: B\u00f6se B\u00fcbchen
+#: xg-go-7.go:23
+msgid "This string has a multilingual comment"
+msgstr ""
+
+#: xg-go-7.go:28
+msgid ""
+"Embedded\n"
+"newline"
+msgstr ""
+
+#. Two backslashes (unlike in Java, where this is just one backslash).
+#: xg-go-7.go:30
+msgid "\\\\"
+msgstr ""
+
+#. A 6-character string (unlike in Java, where this is just one backslash).
+#: xg-go-7.go:32
+msgid "\\u005c"
+msgstr ""
+
+#. A single backslash.
+#: xg-go-7.go:34
+msgid "\\"
+msgstr ""
+
+#. Escape sequences in strings.
+#: xg-go-7.go:36
+msgid ""
+"t -> \t, n -> \n"
+", dquote -> \" ..."
+msgstr ""
+
+#. Hex escapes are recognized.
+#: xg-go-7.go:38
+msgid "bel: \a\n"
+msgstr ""
+
+#. Recognized despite comments
+#. Even across multiline
+#. comment!
+#: xg-go-7.go:41
+msgid "this is a single long line"
+msgstr ""
+
+#. In raw string literals, no escape sequences are recognized.
+#: xg-go-7.go:43
+msgid "raw 2 \\u005c \\\\ \\t \\n \\\""
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-go-7.ok xg-go-7.pot || Exit 1
+
+exit 0
diff --git a/gettext-tools/tests/xgettext-go-8 b/gettext-tools/tests/xgettext-go-8
new file mode 100755 (executable)
index 0000000..f1ffb37
--- /dev/null
@@ -0,0 +1,49 @@
+#!/bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test Go support: plurals.
+
+cat <<\EOF > xg-go-8.go
+package main
+
+import (
+       "fmt"
+       "strconv"
+       "github.com/leonelquinteros/gotext"
+)
+
+func main () {
+       // Specify localedir, domain.
+       gotext.Configure(".", "fr_FR", "hello")
+
+       n := 1
+       /* These two don't work: In the case n = 1, they print
+          "a piece of cake%!(EXTRA int=1)"
+          because fmt.Sprintf treats an unused argument as an error.
+       fmt.Println(gotext.GetN("a piece of cake", "%d pieces of cake", n, n));
+       fmt.Println(fmt.Sprintf(gotext.GetN("a piece of cake", "%d pieces of cake", n), n));
+       */
+       /* These work: In the case n = 1, they consume the strconv.Itoa(n) argument,
+          but don't print it. The trick is to print it as a string, with a precision of zero.  */
+       fmt.Println(gotext.GetN("%.0sa piece of cake", "%s pieces of cake", n, strconv.Itoa(n)));
+       fmt.Println(fmt.Sprintf(gotext.GetN("%.0sa piece of cake", "%s pieces of cake", n), strconv.Itoa(n)));
+}
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --omit-header -d xg-go-8.tmp xg-go-8.go || Exit 1
+LC_ALL=C tr -d '\r' < xg-go-8.tmp.po > xg-go-8.po || Exit 1
+
+cat <<\EOF > xg-go-8.ok
+#: xg-go-8.go:22 xg-go-8.go:23
+#, go-format
+msgid "%.0sa piece of cake"
+msgid_plural "%s pieces of cake"
+msgstr[0] ""
+msgstr[1] ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-go-8.ok xg-go-8.po || Exit 1
+
+exit 0
diff --git a/gettext-tools/tests/xgettext-go-9 b/gettext-tools/tests/xgettext-go-9
new file mode 100755 (executable)
index 0000000..d453474
--- /dev/null
@@ -0,0 +1,41 @@
+#!/bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test Go support: extraction of contexts.
+
+cat <<\EOF > xg-go-9.go
+package main
+
+import (
+       "fmt"
+       "github.com/leonelquinteros/gotext"
+)
+
+func main () {
+       // Specify localedir, domain.
+       gotext.Configure(".", "fr_FR", "hello")
+
+       fmt.Println(gotext.Get("help"))
+       fmt.Println(gotext.GetC("about", "Help"))
+}
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --omit-header -d xg-go-9.tmp xg-go-9.go || Exit 1
+LC_ALL=C tr -d '\r' < xg-go-9.tmp.po > xg-go-9.po || Exit 1
+
+cat <<\EOF > xg-go-9.ok
+#: xg-go-9.go:12
+msgid "help"
+msgstr ""
+
+#: xg-go-9.go:13
+msgctxt "Help"
+msgid "about"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-go-9.ok xg-go-9.po || Exit 1
+
+exit 0
diff --git a/gettext-tools/tests/xgettext-go-stackovfl-1 b/gettext-tools/tests/xgettext-go-stackovfl-1
new file mode 100755 (executable)
index 0000000..df11a69
--- /dev/null
@@ -0,0 +1,63 @@
+#! /bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test Rust support: stack overflow prevented by nesting depth check.
+
+cat <<\EOF > xg-go-so-1.go
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((
+Gettext("Hello!")
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --omit-header --no-location -d xg-go-so-1.tmp xg-go-so-1.go || Exit 1
+LC_ALL=C tr -d '\r' < xg-go-so-1.tmp.po > xg-go-so-1.po || Exit 1
+
+cat <<\EOF > xg-go-so-1.ok
+msgid "Hello!"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-go-so-1.ok xg-go-so-1.po
+result=$?
+
+exit $result
diff --git a/gettext-tools/tests/xgettext-go-stackovfl-2 b/gettext-tools/tests/xgettext-go-stackovfl-2
new file mode 100755 (executable)
index 0000000..a83c0ed
--- /dev/null
@@ -0,0 +1,59 @@
+#! /bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test Rust support: stack overflow prevented by nesting depth check.
+
+cat <<\EOF > xg-go-so-2.go
+import "fmt"
+fmt.Println(
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((((((
+((((((((((((((((((((((((((((((((((((((((((((((
+Gettext("Hello!")
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))))))
+))))))))))))))))))))))))))))))))))))))))))))))
+)
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --omit-header --no-location -d xg-go-so-2.tmp xg-go-so-2.go 2>xg-go-so-2.err
+result=$?
+cat xg-go-so-2.err
+test $result = 1 || Exit 1
+
+exit 0
index 1886968f5751ad3b1f00f3628ecbc988c93795df..1c3db7560fc8e5dc4109e1d986b2377392fcc53e 100644 (file)
@@ -32,6 +32,7 @@ VARIABLE(formatstring_csharp)
 VARIABLE(formatstring_elisp)
 VARIABLE(formatstring_gcc_internal)
 VARIABLE(formatstring_gfc_internal)
+VARIABLE(formatstring_go)
 VARIABLE(formatstring_java)
 VARIABLE(formatstring_java_printf)
 VARIABLE(formatstring_javascript)