groff-commit
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[groff] 14/17: [troff]: Fix Savannah #66387.


From: G. Branden Robinson
Subject: [groff] 14/17: [troff]: Fix Savannah #66387.
Date: Sun, 3 Nov 2024 02:14:24 -0500 (EST)

gbranden pushed a commit to branch master
in repository groff.

commit 1e7bee0221852192adb3634a451d02511c969c3b
Author: G. Branden Robinson <g.branden.robinson@gmail.com>
AuthorDate: Thu Oct 31 03:00:56 2024 -0500

    [troff]: Fix Savannah #66387.
    
    Couple the current hyphenation language more tightly with the
    environment.  This is to ease maintenance of multilingual documents, and
    address a curious situation where an environment could bear an automatic
    hyphenation code that had nothing to do with the selected hyphenation
    language, because the `hla` request has to date never altered the
    environment.
    
    * src/roff/troff/env.h (class environment): Add private member variable
      `language_code` of type `symbol`.  Declare public member functions
      `get_language_code()` and `set_language_code()`.  Declare
      `environment_switch()` as a friend function.
    
    * src/roff/troff/env.cpp (environment::environment): Plain constructor
      initializes `language_code` as empty string.
    
      (environment::environment): Copy constructor copies the language code
      from the source object.
    
      (environment::copy): Member function backing `evc` request copies the
      language code from the source environment.
    
      (environment::print_env): Report the hyphenation language code in use
      by the environment.  Clarify when the automatic hyphenation mode is
      ignored because no hyphenation language is configured in the
      environment.
    
      (select_hyphenation_language): When the `hla` request is called
      without an argument, set the current environment's hyphenation
      language code to the empty string.  With an argument, update the
      environment's hyphenation to the value of the argument.  Add
      assertions prior to function return to enforce invariants: (1) the
      current environment's hyphenation language code must not be null and
      (2) the `current_language` global variable and the current
      environment's hyphenation language code must agree.
    
      (environment_copy, environment_switch): Set the `current_language`
      global variable to current environment's hyphenation language code.
    
      (environment::get_language_code): Implement accessor.
    
      (environment::set_language_code): Implement mutator.
    
    * src/roff/groff/tests/current-language-and-environment-in-sync.sh: Add
      unit test.
    * src/roff/groff/groff.am (groff_TESTS): Run test.
    
    Fixes <https://savannah.gnu.org/bugs/?66387>.
---
 ChangeLog                                          |  44 +++++++
 src/roff/groff/groff.am                            |   1 +
 .../current-language-and-environment-in-sync.sh    | 137 +++++++++++++++++++++
 src/roff/troff/env.cpp                             |  45 ++++++-
 src/roff/troff/env.h                               |   4 +
 5 files changed, 228 insertions(+), 3 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 435f857af..e500abecd 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,47 @@
+2024-10-31  G. Branden Robinson <g.branden.robinson@gmail.com>
+
+       [troff]: Couple the current hyphenation language more tightly
+       with the environment.  This is to ease maintenance of
+       multilingual documents, and address a curious situation where an
+       environment could bear an automatic hyphenation code that had
+       nothing to do with the selected hyphenation language, because
+       the `hla` request has to date never altered the environment.
+
+       * src/roff/troff/env.h (class environment): Add private member
+       variable `language_code` of type `symbol`.  Declare public
+       member functions `get_language_code()` and
+       `set_language_code()`.  Declare `environment_switch()` as a
+       friend function.
+       * src/roff/troff/env.cpp (environment::environment): Plain
+       constructor initializes `language_code` as empty string.
+       (environment::environment): Copy constructor copies the language
+       code from the source object.
+       (environment::copy): Member function backing `evc` request
+       copies the language code from the source environment.
+       (environment::print_env): Report the hyphenation language code
+       in use by the environment.  Clarify when the automatic
+       hyphenation mode is ignored because no hyphenation language is
+       configured in the environment.
+       (select_hyphenation_language): When the `hla` request is called
+       without an argument, set the current environment's hyphenation
+       language code to the empty string.  With an argument, update the
+       environment's hyphenation to the value of the argument.  Add
+       assertions prior to function return to enforce invariants: (1)
+       the current environment's hyphenation language code must not be
+       null and (2) the `current_language` global variable and the
+       current environment's hyphenation language code must agree.
+       (environment_copy, environment_switch): Set the
+       `current_language` global variable to current environment's
+       hyphenation language code.
+       (environment::get_language_code): Implement accessor.
+       (environment::set_language_code): Implement mutator.
+
+       * src/roff/groff/tests/\
+       current-language-and-environment-in-sync.sh: Add unit test.
+       * src/roff/groff/groff.am (groff_TESTS): Run test.
+
+       Fixes <https://savannah.gnu.org/bugs/?66387>.
+
 2024-10-30  G. Branden Robinson <g.branden.robinson@gmail.com>
 
        * src/roff/troff/env.cpp (select_hyphenation_language)
diff --git a/src/roff/groff/groff.am b/src/roff/groff/groff.am
index 351d5b316..3abe3f13d 100644
--- a/src/roff/groff/groff.am
+++ b/src/roff/groff/groff.am
@@ -44,6 +44,7 @@ groff_TESTS = \
   src/roff/groff/tests/backslash-s-works-with-single-digit-argument.sh \
   src/roff/groff/tests/break-zero-length-output-line-sanely.sh \
   src/roff/groff/tests/cf-request-early-does-not-fail.sh \
+  src/roff/groff/tests/current-language-and-environment-in-sync.sh \
   src/roff/groff/tests/degenerate-control-flow-works.sh \
   src/roff/groff/tests/detect-evil-link-time-optimizer.sh \
   src/roff/groff/tests/device-control-escapes-express-basic-latin.sh \
diff --git a/src/roff/groff/tests/current-language-and-environment-in-sync.sh 
b/src/roff/groff/tests/current-language-and-environment-in-sync.sh
new file mode 100755
index 000000000..008f50dda
--- /dev/null
+++ b/src/roff/groff/tests/current-language-and-environment-in-sync.sh
@@ -0,0 +1,137 @@
+#!/bin/sh
+#
+# Copyright (C) 2024 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff 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.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+fail=
+
+wail () {
+  echo "...FAILED" >&2
+  fail=yes
+}
+
+# Unit-test synchronization between the formatter's "current language"
+# (global) and the hyphenation language code in the current environment.
+#
+# See comment prior to `environment_copy()` definition in
+# "src/roff/troff/env.cpp".
+
+input='.
+.tm 1 en=\n[.hla]
+.ev french
+.tm 2 (empty)=\n[.hla]
+.hla fr
+.tm 3 fr=\n[.hla]
+.ev
+.tm 4 en=\n[.hla]
+.ev french
+.tm 5 fr=\n[.hla]
+.ev german
+.tm 6 (empty)=\n[.hla]
+.hla de
+.tm 7 de=\n[.hla]
+.ev no-language
+.tm 8 (empty)=\n[.hla]
+.ev
+.tm 9 de=\n[.hla]
+.ev
+.tm A fr=\n[.hla]
+.ev
+.tm B en=\n[.hla]
+.ev copy
+.tm C (empty)=\n[.hla]
+.evc french
+.tm D fr=\n[.hla]
+.ev no-language
+.tm E (empty)=\n[.hla]
+.hla en
+.tm F en=\n[.hla]
+.'
+
+# Expected output:
+#
+# 1 en=en
+# 2 (empty)=
+# 3 fr=fr
+# 4 en=en
+# 5 fr=fr
+# 6 (empty)=
+# 7 de=de
+# 8 (empty)=
+# 9 de=de
+# A fr=fr
+# B en=en
+# C (empty)=
+# D fr=fr
+# E (empty)=
+# F en=en
+
+output=$(printf "%s\n" "$input" | "$groff" -z 2>&1)
+echo "$output"
+
+echo "verifying that language code on startup is 'en'" >&2
+echo "$output" | grep -Fqx '1 en=en' || wail
+
+echo "verifying that language code in new environment is empty (1)" >&2
+echo "$output" | grep -Fqx '2 (empty)=' || wail
+
+echo "verifying that language code can be set to 'fr'" >&2
+echo "$output" | grep -Fqx '3 fr=fr' || wail
+
+echo "verifying that language code reverts on environment pop (1)" >&2
+echo "$output" | grep -Fqx '4 en=en' || wail
+
+echo "verifying that language code restored on environment push (1)" >&2
+echo "$output" | grep -Fqx '5 fr=fr' || wail
+
+echo "verifying that language code in new environment is empty (2)" >&2
+echo "$output" | grep -Fqx '6 (empty)=' || wail
+
+echo "verifying that language code can be set to 'de'" >&2
+echo "$output" | grep -Fqx '7 de=de' || wail
+
+echo "verifying that language code in new environment is empty (3)" >&2
+echo "$output" | grep -Fqx '8 (empty)=' || wail
+
+echo "verifying that language code restored on environment pop (2)" >&2
+echo "$output" | grep -Fqx '9 de=de' || wail
+
+echo "verifying that language code restored on environment pop (3)" >&2
+echo "$output" | grep -Fqx 'A fr=fr' || wail
+
+echo "verifying that language code restored on environment pop (4)" >&2
+echo "$output" | grep -Fqx 'B en=en' || wail
+
+echo "verifying that language code in new environment is empty (4)" >&2
+echo "$output" | grep -Fqx 'C (empty)=' || wail
+
+echo 'verifying that `evc` request copies language code' >&2
+echo "$output" | grep -Fqx 'D fr=fr' || wail
+
+echo "verifying that empty language code restored on environment push" \
+  >&2
+echo "$output" | grep -Fqx 'E (empty)=' || wail
+
+echo 'verifying that assertion holds when using `hla` request' >&2
+echo "$output" | grep -Fqx 'F en=en' || wail
+
+test -z "$fail"
+
+# vim:set autoindent expandtab shiftwidth=2 tabstop=2 textwidth=72:
diff --git a/src/roff/troff/env.cpp b/src/roff/troff/env.cpp
index 5de7897d5..cff3abea0 100644
--- a/src/roff/troff/env.cpp
+++ b/src/roff/troff/env.cpp
@@ -787,6 +787,7 @@ environment::environment(symbol nm)
   line_number_indent(0),
   line_number_multiple(1),
   no_number_count(0),
+  language_code(""),
   hyphenation_mode(1),
   hyphenation_mode_default(1),
   hyphen_line_count(0),
@@ -881,6 +882,7 @@ environment::environment(const environment *e)
   line_number_indent(e->line_number_indent),
   line_number_multiple(e->line_number_multiple),
   no_number_count(e->no_number_count),
+  language_code(e->language_code),
   hyphenation_mode(e->hyphenation_mode),
   hyphenation_mode_default(e->hyphenation_mode_default),
   hyphen_line_count(0),
@@ -968,6 +970,7 @@ void environment::copy(const environment *e)
   no_number_count = e->no_number_count;
   tab_char = e->tab_char;
   leader_char = e->leader_char;
+  set_language_code(e->language_code.contents());
   hyphenation_mode = e->hyphenation_mode;
   hyphenation_mode_default = e->hyphenation_mode_default;
   fontno = e->fontno;
@@ -3542,6 +3545,10 @@ void environment::print_env()
     errprint("  lines remaining for which to suppress numbering: %1\n",
             no_number_count);
   }
+  const char *hl = language_code.contents();
+  bool is_hyphenation_impossible = language_code.is_empty();
+  errprint("  hyphenation language code: %1\n",
+          is_hyphenation_impossible ? "(none)" : hl);
   string hf = hyphenation_mode ? "on" : "off";
   if (hyphenation_mode & HYPHEN_NOT_LAST_LINE)
     hf += ", not on line before vertical position trap";
@@ -3554,8 +3561,10 @@ void environment::print_env()
   if (hyphenation_mode & HYPHEN_NOT_FIRST_CHARS)
     hf += ", not allowed within first two characters";
   hf += '\0';
-  errprint("  hyphenation mode: %1 (%2)\n", hyphenation_mode,
-          hf.contents());
+  errprint("  hyphenation mode: %1 (%2)%3\n", hyphenation_mode,
+          hf.contents(),
+          is_hyphenation_impossible ? " [no hyphenation language]"
+                                    : "");
   errprint("  hyphenation mode default: %1\n",
           hyphenation_mode_default);
   errprint("  count of consecutive hyphenated lines: %1\n",
@@ -3642,6 +3651,7 @@ static void select_hyphenation_language()
 {
   if (!has_arg()) {
     current_language = 0 /* nullptr */;
+    curenv->set_language_code("");
     skip_line();
     return;
   }
@@ -3654,10 +3664,22 @@ static void select_hyphenation_language()
       (void) language_dictionary.lookup(nm,
        static_cast<hyphenation_language *>(current_language));
     }
+    curenv->set_language_code(nm.contents());
   }
+  assert(!(curenv->get_language_code().is_null()));
+  if (current_language != 0 /* nullptr */)
+    assert(strcmp(current_language->name.contents(),
+                 curenv->get_language_code().contents()) == 0);
   skip_line();
 }
 
+// The environment class has no visibility into which hyphenation
+// languages are defined; it has only a code that is either empty
+// or must reference a valid one (and since the code uniquely
+// identifies a language, a `const char *` is more ergonomic than a
+// pointer to a type that's not visible in the class's scope).  So
+// updating that code is a two-step process.
+
 void environment_copy()
 {
   if (!has_arg()) {
@@ -3671,14 +3693,27 @@ void environment_copy()
   symbol nm = get_long_name();
   assert(nm != 0 /* nullptr */);
   e = static_cast<environment *>(env_dictionary.lookup(nm));
-  if (e != 0 /* nullptr */)
+  if (e != 0 /* nullptr */) {
     curenv->copy(e);
+    current_language = static_cast<hyphenation_language *>
+      (language_dictionary.lookup(e->get_language_code()));
+  }
   else
     error("cannot copy from nonexistent environment '%1'",
          nm.contents());
   skip_line();
 }
 
+symbol environment::get_language_code()
+{
+  return language_code;
+}
+
+void environment::set_language_code(const char *lang)
+{
+    language_code = lang;
+}
+
 void environment_switch()
 {
   if (curenv->is_dummy()) {
@@ -3694,6 +3729,8 @@ void environment_switch()
        bool seen_eol   = curenv->seen_eol;
        bool suppress_next_eol = curenv->suppress_next_eol;
        curenv = env_stack->env;
+       current_language = static_cast<hyphenation_language *>
+         (language_dictionary.lookup(curenv->language_code));
        curenv->seen_space = seen_space;
        curenv->seen_eol   = seen_eol;
        curenv->suppress_next_eol = suppress_next_eol;
@@ -3711,6 +3748,8 @@ void environment_switch()
       }
       env_stack = new env_list_node(curenv, env_stack);
       curenv = e;
+      current_language = static_cast<hyphenation_language *>
+       (language_dictionary.lookup(curenv->language_code));
     }
   }
   skip_line();
diff --git a/src/roff/troff/env.h b/src/roff/troff/env.h
index 6b4ec03f9..1d70f5529 100644
--- a/src/roff/troff/env.h
+++ b/src/roff/troff/env.h
@@ -212,6 +212,7 @@ class environment {
   int line_number_indent;      // in digit spaces
   int line_number_multiple;
   int no_number_count;
+  symbol language_code;
   unsigned hyphenation_mode;
   unsigned hyphenation_mode_default;
   int hyphen_line_count;
@@ -312,6 +313,8 @@ public:
   hunits get_input_line_position();
   const char *get_tabs();
   int is_using_line_tabs();
+  symbol get_language_code();
+  void set_language_code(const char *);
   unsigned get_hyphenation_mode();
   unsigned get_hyphenation_mode_default();
   int get_hyphen_line_max();
@@ -395,6 +398,7 @@ public:
   friend void number_lines();
   friend void leader_character();
   friend void tab_character();
+  friend void environment_switch();
   friend void hyphenate_request();
   friend void set_hyphenation_mode_default();
   friend void no_hyphenate();



reply via email to

[Prev in Thread] Current Thread [Next in Thread]