From 6d5a50fc4894d120441549762a5fc173b1fcc249 Mon Sep 17 00:00:00 2001 From: Bruno Haible Date: Wed, 23 Jul 2025 09:37:23 +0200 Subject: [PATCH] OCaml support: Add OCaml support in xgettext. * gettext-tools/doc/lang-ocaml.texi: New file. * gettext-tools/doc/Makefile.am (gettext_TEXINFOS): Add it. * gettext-tools/doc/gettext.texi (No string concatenation): Mention string concatenation in OCaml. (List of Programming Languages): Include lang-ocaml.texi. * gettext-tools/doc/xgettext.texi: Document the -L OCaml option. * gettext-tools/src/x-ocaml.h: New file. * gettext-tools/src/x-ocaml.c: New file. * gettext-tools/src/xgettext.c: Include x-ocaml.h. (flag_table_ocaml): New variable. (main): Invoke init_flag_table_ocaml, x_ocaml_extract_all, x_ocaml_keyword. (usage): Document the -L OCaml option. (language_to_extractor, extension_to_language): Support OCaml. * gettext-tools/src/FILES: Mention x-ocaml.h, x-ocaml.c. * gettext-tools/src/Makefile.am (noinst_HEADERS): Add x-ocaml.h. (xgettext_SOURCES): Add x-ocaml.c. * gettext-tools/po/POTFILES.in: Add src/x-ocaml.c. --- gettext-tools/doc/Makefile.am | 1 + gettext-tools/doc/gettext.texi | 7 ++ gettext-tools/doc/lang-ocaml.texi | 59 +++++++++++++ gettext-tools/doc/xgettext.texi | 1 + gettext-tools/po/POTFILES.in | 1 + gettext-tools/src/FILES | 3 + gettext-tools/src/Makefile.am | 2 + gettext-tools/src/x-ocaml.c | 142 ++++++++++++++++++++++++++++++ gettext-tools/src/x-ocaml.h | 50 +++++++++++ gettext-tools/src/xgettext.c | 8 +- 10 files changed, 273 insertions(+), 1 deletion(-) create mode 100644 gettext-tools/doc/lang-ocaml.texi create mode 100644 gettext-tools/src/x-ocaml.c create mode 100644 gettext-tools/src/x-ocaml.h diff --git a/gettext-tools/doc/Makefile.am b/gettext-tools/doc/Makefile.am index e725e430f..9162a8f12 100644 --- a/gettext-tools/doc/Makefile.am +++ b/gettext-tools/doc/Makefile.am @@ -85,6 +85,7 @@ gettext_TEXINFOS = \ lang-pascal.texi \ lang-modula2.texi \ lang-d.texi \ + lang-ocaml.texi \ lang-smalltalk.texi \ lang-vala.texi \ lang-wxwidgets.texi \ diff --git a/gettext-tools/doc/gettext.texi b/gettext-tools/doc/gettext.texi index 4fd819e9e..9b5018f35 100644 --- a/gettext-tools/doc/gettext.texi +++ b/gettext-tools/doc/gettext.texi @@ -461,6 +461,7 @@ Individual Programming Languages * Lua:: Lua * Pascal:: Pascal - Free Pascal Compiler * Modula-2:: Modula-2 +* OCaml:: OCaml * Smalltalk:: GNU Smalltalk * Vala:: Vala * wxWidgets:: wxWidgets library @@ -2522,6 +2523,7 @@ at runtime (or possibly at compile time, if the compiler supports that). @cindex Lua, string concatenation @cindex Modula-2, string concatenation @cindex D, string concatenation +@cindex OCaml, string concatenation @cindex Smalltalk, string concatenation @cindex Vala, string concatenation @cindex Perl, string concatenation @@ -2567,6 +2569,9 @@ In Modula-2, string concatenation is denoted by the @samp{+} operator. In D, string concatenation is denoted by the @samp{~} operator. @c Reference: https://dlang.org/spec/expression.html#cat_expressions @item +In OCaml, string concatenation is denoted by the @samp{^} operator. +@c Reference: https://ocaml.org/manual/5.3/api/String.html#concat +@item In Smalltalk, string concatenation is denoted by the @samp{,} operator. @c Reference: https://rmod-files.lille.inria.fr/FreeBooks/ByExample/14%20-%20Chapter%2012%20-%20Strings.pdf @item @@ -10789,6 +10794,7 @@ that language, and to combine the resulting files using @code{msgcat}. * Pascal:: Pascal - Free Pascal Compiler * Modula-2:: Modula-2 * D:: D +* OCaml:: OCaml * Smalltalk:: GNU Smalltalk * Vala:: Vala * wxWidgets:: wxWidgets library @@ -10821,6 +10827,7 @@ that language, and to combine the resulting files using @code{msgcat}. @include lang-pascal.texi @include lang-modula2.texi @include lang-d.texi +@include lang-ocaml.texi @include lang-smalltalk.texi @include lang-vala.texi @include lang-wxwidgets.texi diff --git a/gettext-tools/doc/lang-ocaml.texi b/gettext-tools/doc/lang-ocaml.texi new file mode 100644 index 000000000..24e76a90f --- /dev/null +++ b/gettext-tools/doc/lang-ocaml.texi @@ -0,0 +1,59 @@ +@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 OCaml +@subsection OCaml +@cindex OCaml + +@table @asis +@item RPMs +ocaml, ocaml-gettext-devel + +@item Ubuntu packages +ocaml, opam +@*@code{opam install gettext-stub} + +@item File extension +@code{ml} + +@item String syntax +@code{"abc"} + +@item gettext shorthand +@code{(s_ "abc")} or, for format strings, @code{(f_ "abc")} + +@item gettext/ngettext functions +@code{s_}, @code{f_}, @code{sn_}, @code{fn_} + +@item textdomain +@code{textdomain} field in first parameter of @code{Gettext.Program} + +@item bindtextdomain +@code{dir} field in first parameter of @code{Gettext.Program} + +@item setlocale +--- + +@item Prerequisite +Declare the libraries @code{gettext.base} and @code{gettext-stub} +in the @samp{dune} file. + +@item Use or emulate GNU gettext +Use (assuming that you pass @code{GettextStub.Native} +as second parameter of @code{Gettext.Program}) + +@item Extractor +@code{xgettext} + +@item Formatting with positions +--- + +@item Portability +fully portable + +@item po-mode marking +--- +@end table + +@c An example is available in the @file{examples} directory: @code{hello-ocaml}. diff --git a/gettext-tools/doc/xgettext.texi b/gettext-tools/doc/xgettext.texi index 72063c226..423051bde 100644 --- a/gettext-tools/doc/xgettext.texi +++ b/gettext-tools/doc/xgettext.texi @@ -95,6 +95,7 @@ Specifies the language of the input files. The supported languages are @code{Lua}, @code{Modula-2}, @code{D}, +@code{OCaml}, @code{Smalltalk}, @code{Vala}, @code{Tcl}, diff --git a/gettext-tools/po/POTFILES.in b/gettext-tools/po/POTFILES.in index cea4a4176..0a2f0e1a9 100644 --- a/gettext-tools/po/POTFILES.in +++ b/gettext-tools/po/POTFILES.in @@ -112,6 +112,7 @@ src/x-librep.c src/x-lisp.c src/x-lua.c src/x-modula2.c +src/x-ocaml.c src/x-perl.c src/x-php.c src/x-po.c diff --git a/gettext-tools/src/FILES b/gettext-tools/src/FILES index 9d900a867..b34f0cee3 100644 --- a/gettext-tools/src/FILES +++ b/gettext-tools/src/FILES @@ -422,6 +422,9 @@ msgl-check.c | x-d.c | html5-entities.h | String extractor for D. +| x-ocaml.h +| x-ocaml.c +| String extractor for OCaml. | x-smalltalk.h | x-smalltalk.c | String extractor for Smalltalk. diff --git a/gettext-tools/src/Makefile.am b/gettext-tools/src/Makefile.am index 1b7faf125..0e66d1571 100644 --- a/gettext-tools/src/Makefile.am +++ b/gettext-tools/src/Makefile.am @@ -91,6 +91,7 @@ noinst_HEADERS = \ x-lua.h \ x-modula2.h \ x-d.h html5-entities.h \ + x-ocaml.h \ x-smalltalk.h \ x-vala.h \ x-tcl.h \ @@ -333,6 +334,7 @@ xgettext_SOURCES += \ x-lua.c \ x-modula2.c \ x-d.c \ + x-ocaml.c \ x-smalltalk.c \ x-vala.c \ x-tcl.c \ diff --git a/gettext-tools/src/x-ocaml.c b/gettext-tools/src/x-ocaml.c new file mode 100644 index 000000000..2a6b021f9 --- /dev/null +++ b/gettext-tools/src/x-ocaml.c @@ -0,0 +1,142 @@ +/* xgettext OCaml backend. + Copyright (C) 2020-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 "config.h" +#endif + +/* Specification. */ +#include "x-ocaml.h" + +#include +#include +#include + +#include +#include "message.h" +#include "clean-temp.h" +#include "concat-filename.h" +#include "execute.h" +#include "xvasprintf.h" +#include "x-po.h" +#include "xgettext.h" +#include "gettext.h" + +/* A convenience macro. I don't like writing gettext() every time. */ +#define _(str) gettext (str) + +/* We don't parse OCaml directly, but instead rely on the 'ocaml-gettext' + program, that invokes the 'ocaml-xgettext' program. Both are contained + in the 'opam' package named 'gettext': + https://opam.ocaml.org/packages/gettext/ + https://github.com/gildor478/ocaml-gettext + https://github.com/gildor478/ocaml-gettext/blob/master/doc/reference-manual.md + + Comments start with '(*' and end with '*)' and can be nested. + Reference: + */ + + +/* ====================== Keyword set customization. ====================== */ + +/* This function currently has no effect. */ +void +x_ocaml_extract_all (void) +{ +} + +/* This function currently has no effect. */ +void +x_ocaml_keyword (const char *keyword) +{ +} + +/* This function currently has no effect. */ +void +init_flag_table_ocaml (void) +{ +} + + +/* ========================= Extracting strings. ========================== */ + +static bool +is_not_header (const message_ty *mp) +{ + return !is_header (mp); +} + +void +extract_ocaml (const char *found_in_dir, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + /* Invoke + ocaml-gettext --action extract --extract-pot .pot real_filename */ + + /* First, create a temporary directory where this invocation can place its + output. */ + struct temp_dir *tmpdir = create_temp_dir ("ocgt", NULL, false); + if (tmpdir == NULL) + exit (EXIT_FAILURE); + + /* Prepare the temporary POT file name. */ + char *temp_file_name = xconcatenated_filename (tmpdir->dir_name, "temp.pot", NULL); + register_temp_file (tmpdir, temp_file_name); + + /* Invoke ocaml-gettext. */ + const char *progname = "ocaml-gettext"; + { + const char *argv[7]; + int exitstatus; + /* Prepare arguments. */ + argv[0] = progname; + argv[1] = "--action"; + argv[2] = "extract"; + argv[3] = "--extract-pot"; + argv[4] = temp_file_name; + argv[5] = logical_filename; + argv[6] = NULL; + exitstatus = execute (progname, progname, argv, NULL, found_in_dir, + true, false, false, false, true, false, NULL); + if (exitstatus != 0) + error (EXIT_FAILURE, 0, _("%s subprocess failed with exit code %d"), + progname, exitstatus); + } + + /* Read the resulting POT file. */ + { + FILE *fp = fopen (temp_file_name, "r"); + if (fp == NULL) + error (EXIT_FAILURE, 0, _("%s subprocess did not create the expected file"), + progname); + char *dummy_filename = xasprintf (_("(output from '%s')"), progname); + extract_po (fp, temp_file_name, dummy_filename, flag_table, mdlp); + fclose (fp); + free (dummy_filename); + } + + cleanup_temp_dir (tmpdir); + + if (xgettext_omit_header) + { + /* Remove the header entry. */ + if (mdlp->nitems > 0) + message_list_remove_if_not (mdlp->item[0]->messages, is_not_header); + } +} diff --git a/gettext-tools/src/x-ocaml.h b/gettext-tools/src/x-ocaml.h new file mode 100644 index 000000000..2efa4574f --- /dev/null +++ b/gettext-tools/src/x-ocaml.h @@ -0,0 +1,50 @@ +/* xgettext OCaml backend. + Copyright (C) 2020-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 . */ + + +#include + +#include "message.h" +#include "xg-arglist-context.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_OCAML \ + { "ml", "OCaml" }, \ + +#define SCANNERS_OCAML \ + { "OCaml", NULL, extract_ocaml, \ + NULL, NULL, NULL }, \ + +extern void extract_ocaml (const char *found_in_dir, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + +extern void x_ocaml_keyword (const char *keyword); +extern void x_ocaml_extract_all (void); + +extern void init_flag_table_ocaml (void); + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/xgettext.c b/gettext-tools/src/xgettext.c index 11a5c7c88..92b77add0 100644 --- a/gettext-tools/src/xgettext.c +++ b/gettext-tools/src/xgettext.c @@ -127,6 +127,7 @@ #include "x-lua.h" #include "x-modula2.h" #include "x-d.h" +#include "x-ocaml.h" #include "x-smalltalk.h" #include "x-vala.h" #include "x-tcl.h" @@ -369,6 +370,7 @@ main (int argc, char *argv[]) init_flag_table_lua (); init_flag_table_modula2 (); init_flag_table_d (); + init_flag_table_ocaml (); init_flag_table_vala (); init_flag_table_tcl (); init_flag_table_perl (); @@ -466,6 +468,7 @@ main (int argc, char *argv[]) x_lua_extract_all (); x_modula2_extract_all (); x_d_extract_all (); + x_ocaml_extract_all (); x_vala_extract_all (); x_tcl_extract_all (); x_perl_extract_all (); @@ -552,6 +555,7 @@ main (int argc, char *argv[]) x_lua_keyword (optarg); x_modula2_keyword (optarg); x_d_keyword (optarg); + x_ocaml_keyword (optarg); x_vala_keyword (optarg); x_tcl_keyword (optarg); x_perl_keyword (optarg); @@ -1185,7 +1189,7 @@ Choice of input file language:\n")); (C, C++, ObjectiveC, PO, Python, Java,\n\ JavaProperties, C#, JavaScript, TypeScript, TSX,\n\ Scheme, Guile, Lisp, EmacsLisp, librep, Rust,\n\ - Go, Ruby, Shell, awk, Lua, Modula-2, D,\n\ + Go, Ruby, Shell, awk, Lua, Modula-2, D, OCaml,\n\ Smalltalk, Vala, Tcl, Perl, PHP, GCC-source,\n\ YCP, NXStringTable, RST, RSJ, Glade, GSettings,\n\ Desktop)\n")); @@ -2513,6 +2517,7 @@ language_to_extractor (const char *name) SCANNERS_LUA SCANNERS_MODULA2 SCANNERS_D + SCANNERS_OCAML SCANNERS_SMALLTALK SCANNERS_VALA SCANNERS_TCL @@ -2612,6 +2617,7 @@ extension_to_language (const char *extension) EXTENSIONS_LUA EXTENSIONS_MODULA2 EXTENSIONS_D + EXTENSIONS_OCAML EXTENSIONS_SMALLTALK EXTENSIONS_VALA EXTENSIONS_TCL -- 2.47.3