From: Bruno Haible Date: Fri, 7 Mar 2025 11:24:50 +0000 (+0100) Subject: Add Go support. X-Git-Tag: v0.25~97 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=893cde22c587388861b77557adcf3cf1e4691019;p=thirdparty%2Fgettext.git Add Go support. * 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. --- diff --git a/NEWS b/NEWS index 01415bc98..9a2d88ce7 100644 --- 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: diff --git a/autogen.sh b/autogen.sh index 5c39845a4..24e2c61a3 100755 --- a/autogen.sh +++ b/autogen.sh @@ -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 ' diff --git a/autopull.sh b/autopull.sh index 87dd47806..2b39f66c3 100755 --- a/autopull.sh +++ b/autopull.sh @@ -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 <.c so that it works with dnl a newer version of tree-sitter-. . $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" diff --git a/gettext-tools/doc/Makefile.am b/gettext-tools/doc/Makefile.am index bc43d4b23..8d975c673 100644 --- a/gettext-tools/doc/Makefile.am +++ b/gettext-tools/doc/Makefile.am @@ -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 \ diff --git a/gettext-tools/doc/gettext.texi b/gettext-tools/doc/gettext.texi index 0fa7087ff..edc27dc7d 100644 --- a/gettext-tools/doc/gettext.texi +++ b/gettext-tools/doc/gettext.texi @@ -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 index 000000000..fe7ea2adc --- /dev/null +++ b/gettext-tools/doc/lang-go.texi @@ -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 ). +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 diff --git a/gettext-tools/doc/xgettext.texi b/gettext-tools/doc/xgettext.texi index 21306489f..b6b4e9801 100644 --- a/gettext-tools/doc/xgettext.texi +++ b/gettext-tools/doc/xgettext.texi @@ -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, diff --git a/gettext-tools/libgettextpo/Makefile.am b/gettext-tools/libgettextpo/Makefile.am index 85e32df22..25731ecd0 100644 --- a/gettext-tools/libgettextpo/Makefile.am +++ b/gettext-tools/libgettextpo/Makefile.am @@ -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 \ diff --git a/gettext-tools/src/FILES b/gettext-tools/src/FILES index 554667952..e7f99d19f 100644 --- a/gettext-tools/src/FILES +++ b/gettext-tools/src/FILES @@ -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. diff --git a/gettext-tools/src/Makefile.am b/gettext-tools/src/Makefile.am index 27d234092..12285c3dc 100644 --- a/gettext-tools/src/Makefile.am +++ b/gettext-tools/src/Makefile.am @@ -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 index 000000000..ae651afa4 --- /dev/null +++ b/gettext-tools/src/format-go.c @@ -0,0 +1,730 @@ +/* Go format strings. + Copyright (C) 2001-2025 Free Software Foundation, Inc. + Written by Bruno Haible , 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 . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#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 + 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 + +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 */ diff --git a/gettext-tools/src/format.c b/gettext-tools/src/format.c index ff5f56d26..e713586eb 100644 --- a/gettext-tools/src/format.c +++ b/gettext-tools/src/format.c @@ -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, diff --git a/gettext-tools/src/format.h b/gettext-tools/src/format.h index fe0199b8a..1634bfbbf 100644 --- a/gettext-tools/src/format.h +++ b/gettext-tools/src/format.h @@ -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; diff --git a/gettext-tools/src/message.c b/gettext-tools/src/message.c index ea1f61c02..2c6d94f5b 100644 --- a/gettext-tools/src/message.c +++ b/gettext-tools/src/message.c @@ -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", diff --git a/gettext-tools/src/message.h b/gettext-tools/src/message.h index c7c9699ff..dd9447d64 100644 --- a/gettext-tools/src/message.h +++ b/gettext-tools/src/message.h @@ -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 index 000000000..4a0261931 --- /dev/null +++ b/gettext-tools/src/x-go.c @@ -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 . */ + +/* Written by Bruno Haible , 2025. */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "x-go.h" + +#include +#include +#include +#include +#include +#include +#include + +#define SBR_NO_PREPENDF +#include +#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: */ +#include +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 + */ + + /* 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 + */ + + /* 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 (¤t_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 (¤t_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 (¤t_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 (¤t_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 (¤t_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 (¤t_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 (¤t_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. */ + 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 (¤t_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 (¤t_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 (¤t_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 (¤t_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 (¤t_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. + */ + 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 index 000000000..2206ebb24 --- /dev/null +++ b/gettext-tools/src/x-go.h @@ -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 . */ + +/* Written by Bruno Haible , 2025. */ + + +#include + +#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 diff --git a/gettext-tools/src/xgettext.c b/gettext-tools/src/xgettext.c index a87f0325e..c26dd0b03 100644 --- a/gettext-tools/src/xgettext.c +++ b/gettext-tools/src/xgettext.c @@ -112,6 +112,7 @@ #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 diff --git a/gettext-tools/tests/Makefile.am b/gettext-tools/tests/Makefile.am index b48b6d37d..ab8710d11 100644 --- a/gettext-tools/tests/Makefile.am +++ b/gettext-tools/tests/Makefile.am @@ -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 index 000000000..396fd7c97 --- /dev/null +++ b/gettext-tools/tests/format-go-1 @@ -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 < 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 index 000000000..987d1fc80 --- /dev/null +++ b/gettext-tools/tests/format-go-2 @@ -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 < 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 index 000000000..fd30c507a --- /dev/null +++ b/gettext-tools/tests/xgettext-go-1 @@ -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 index 000000000..abcca79ca --- /dev/null +++ b/gettext-tools/tests/xgettext-go-10 @@ -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 index 000000000..703d60162 --- /dev/null +++ b/gettext-tools/tests/xgettext-go-11 @@ -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 index 000000000..70e7649f9 --- /dev/null +++ b/gettext-tools/tests/xgettext-go-12 @@ -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 index 000000000..21564004a --- /dev/null +++ b/gettext-tools/tests/xgettext-go-13 @@ -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 index 000000000..b2ea17d94 --- /dev/null +++ b/gettext-tools/tests/xgettext-go-14 @@ -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 index 000000000..bf939eb6c --- /dev/null +++ b/gettext-tools/tests/xgettext-go-15 @@ -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 index 000000000..9a7ed8aa7 --- /dev/null +++ b/gettext-tools/tests/xgettext-go-16 @@ -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 index 000000000..fc1bab0d9 --- /dev/null +++ b/gettext-tools/tests/xgettext-go-17 @@ -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 index 000000000..5f975de99 --- /dev/null +++ b/gettext-tools/tests/xgettext-go-18 @@ -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 index 000000000..e0b3662ee --- /dev/null +++ b/gettext-tools/tests/xgettext-go-2 @@ -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 index 000000000..48db28a04 --- /dev/null +++ b/gettext-tools/tests/xgettext-go-3 @@ -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 index 000000000..aea8dd78c --- /dev/null +++ b/gettext-tools/tests/xgettext-go-4 @@ -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 index 000000000..69ff4e56a --- /dev/null +++ b/gettext-tools/tests/xgettext-go-5 @@ -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 index 000000000..073d16f97 --- /dev/null +++ b/gettext-tools/tests/xgettext-go-6 @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 index 000000000..aadd1ef45 --- /dev/null +++ b/gettext-tools/tests/xgettext-go-7 @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 index 000000000..f1ffb37a8 --- /dev/null +++ b/gettext-tools/tests/xgettext-go-8 @@ -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 index 000000000..d4534742e --- /dev/null +++ b/gettext-tools/tests/xgettext-go-9 @@ -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 index 000000000..df11a69b6 --- /dev/null +++ b/gettext-tools/tests/xgettext-go-stackovfl-1 @@ -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 index 000000000..a83c0ed7a --- /dev/null +++ b/gettext-tools/tests/xgettext-go-stackovfl-2 @@ -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 diff --git a/gettext-tools/woe32dll/gettextsrc-exports.c b/gettext-tools/woe32dll/gettextsrc-exports.c index 1886968f5..1c3db7560 100644 --- a/gettext-tools/woe32dll/gettextsrc-exports.c +++ b/gettext-tools/woe32dll/gettextsrc-exports.c @@ -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)