Proposed: `rhw` request to remove hyphenation exception words

"G. Branden Robinson" <[email protected]> Wed, 1 Apr 2026 18:38:40 -0500
Newsgroups gmane.comp.printing.groff.general
Message-ID <20260401233840.eulhor2lkpnc4tih@illithid>
Hi folks,

This was discussed three years ago and, hearing no objection at the
time, I'm working on it.

At 2023-03-26T01:15:27-0500, G. Branden Robinson wrote:
[...]
> My net takeaway from this is that it is indeed better to keep
> hyphenation overrides within individual man pages.  But maybe the only
> way to know how tedious this really is to see how much a large,
> practical corpus of man pages, like the Linux man-pages, requires it.
[...]
> One of the themes of my suggested revisions to GNU troff has been to
> provide ways to unwind or reset things that historically haven't been
> available.  One of those is environment removal (Savannah #60954).
> 
> Another that has occurred to me is hyphenation override removal.
> 
> Today, invoking the `hw` request without arguments does nothing.  We
> could change it to clear any existing hyphenation overrides.
> 
> Or, perhaps better, we could add an 'hwrm' or 'rhw' request; if given
> arguments, it reads each word (ignoring hyphens), matches it against
> the existing list of overrides, and removes the word if found.  If
> given no arguments, it removes all overrides.  Then, an.tmac (and
> doc.tmac) could call it when hitting `TH` (and `Dd`) macros, tidying
> up the state of the formatter for the next document.

Nota bene: the only hyphenation exception words removable with `rhw`
will be those added with `hw`.  Those populated by language-specific
hyphenation pattern exception files like "tmac/hyphenex.{cs,en,pl}" will
be immune to such removal.

I feel more strongly now that `rhw` is the best name for this request;
it's consistent with `rchar`, `rfschar`, and resembles `rr` and `rm`.

Thoughts?  Objections?

Implementation sketch:

diff --git a/src/roff/troff/dictionary.cpp b/src/roff/troff/dictionary.cpp
index 4aebc1f92..34691aa74 100644
--- a/src/roff/troff/dictionary.cpp
+++ b/src/roff/troff/dictionary.cpp
@@ -134,7 +134,8 @@ bool dictionary_iterator::get(symbol *sp, void **vp)
   for (; i < dict->size; i++)
     if (dict->table[i].v) {
       *sp = dict->table[i].s;
-      *vp = dict->table[i].v;
+      if (vp != 0 /* nullptr */)
+	*vp = dict->table[i].v;
       i++;
       return true;
     }
diff --git a/src/roff/troff/env.cpp b/src/roff/troff/env.cpp
index 1c9ef9b02..17629cb9a 100644
--- a/src/roff/troff/env.cpp
+++ b/src/roff/troff/env.cpp
@@ -3953,6 +3953,34 @@ static void add_hyphenation_exception_words_request() // .hw
   skip_line();
 }
 
+static void remove_hyphenation_exception_words_request() // .rhw
+{
+  if (0 /* nullptr */ == current_language) {
+    error("cannot remove hyphenation exception words when no"
+	  " hyphenation language is selected");
+    skip_line();
+    return;
+  }
+  dictionary_iterator iter(current_language->exceptions);
+  symbol entry;
+  if (!has_arg()) {
+    debug("GBR: remove_hyphenation_exception_words_request(): nuking 'em all");
+    while (iter.get(&entry, 0 /* nullptr */)) {
+      assert(!entry.is_null());
+      // The exception word symbol's contents contains a space if it's
+      // _not_ user-defined.  Kind of kludgy, but possibly not worth
+      // fixing without also migrating to an STL unordered_map or
+      // similar, and using a `struct` with a string and a `bool` in it
+      // as the values.
+      if (!strchr(entry.contents(), ' ')) {
+	debug("GBR: nuking '%1'", entry.contents());
+	current_language->exceptions.remove(entry.contents());
+      }
+    }
+  }
+  skip_line();
+}
+
 static void print_hyphenation_exceptions_request() // .phw
 {
   if (0 /* nullptr */ == current_language) {
@@ -4695,6 +4723,7 @@ void init_hyphenation_pattern_requests()
   init_request("hpfa", append_hyphenation_patterns_from_file_request);
   init_request("hw", add_hyphenation_exception_words_request);
   init_request("phw", print_hyphenation_exceptions_request);
+  init_request("rhw", remove_hyphenation_exception_words_request);
 }
 
 // Local Variables:

Regards,
Branden
signature.asc (application/pgp-signature, 833 B)
-----BEGIN PGP SIGNATURE-----

iQIzBAABCAAdFiEEh3PWHWjjDgcrENwa0Z6cfXEmbc4FAmnNrHkACgkQ0Z6cfXEm
bc6/wBAAnSdABqL7J38dBpAg+4D/wjv6y4mj1vs+mgRaPNAl9h6ocK6Omv/83sqP
cfir31pG+6m1LhIJyWbc4ri2CQRssS994W7M9BoHN7m6z28OPDrigAgVpxmBYbAr
mFUsECr6drsUXvwuagL1FUOD+/S4i10Yanwb2Lpxdf+nPI6X7l2vgZ49yHGUFIj7
oyvKjl+sqmr0XfwftblEUSe27prHtitQ0l/XpiPDs2kGcOFLMPrwCzxfs+9SXsNR
PRN+7owHOBcHvuS4nI5R8b7OTIWpGWsyAlz1zRpREMi27Q+w1sJbngL5Lsq9+XrX
6/B5ko6jgN3sGd06qIqRTREeLRYq08E8LaZYHAhd0c1CZ8eP96kW3gdyJ4iz/HKq
wGPbDkNU73hubiBaYCkd+cI/EX19qfVbfs3l089stWKZfHFEqOSXcsa9YPBOAZ6E
gGUvgkCJNIo77zn/v04Q0wyPiLIV/nOWT5JbNQYV3yrAdmAc0xtSUZbjghOpHV3O
NUwLXcqM49c42+0qOFDdBtfCEDyYH9I2a/N7ZqGPWFYr8HA55ozd5YZe7LZ4MpfV
2Y4HiNeo6NxQSJuBcLliciMdZQit+mTlMd0ke6/opfvZVke1XolErWH9ct9ske9A
+a5Tv1TZppLem7NYAdp7JEAomBHhVMmCaNutQ/WXo14XIlQ31N8=
=F6B8
-----END PGP SIGNATURE-----