Hi, John!

First, let me address your lingering point.

At 2020-04-12T13:34:04+1000, John Gardner wrote:
> Wouldn't it be simpler to inline the contents of unicode.tmac? Only
> two other macro packages reference it, and the file is arguably short
> enough not to violate any DRY principles:
> 
> λ GNU-Groff (master): grep -rnw ./tmac -e unicode.tmac
> ./tmac/html.tmac:546:.mso unicode.tmac
> ./tmac/tmac.am:62:  tmac/unicode.tmac \
> ./tmac/tty.tmac:72:.  mso unicode.tmac
> ./tmac/unicode.tmac:1:.\" unicode.tmac
> 
> unicode.tmac has only 3 lines of (relevant) source code:
> 
> .char - \[hy]
> .char ` \[oq]
> .char ' \[cq]

We could do that.  I suspect the idea was that unicode.tmac might grow
in the future, causing increasing affront to the DRY principle.

However, I think doing so simply papers over a deeper and more insidious
issue, where groff file nesting, compatibility mode, and the historical
legacy of AT&T troff's two-character register namespace (because that's
all you'll ever need :-| ).

I've snipped my original plan because it did not survive contact with
the enemy.  What I ended up with was a small change to the parser to
support a new register.  I decided to document my justification in
groff_diff(7); here it is.

***

The register \n[.cp] is specialized and may require a statement of
rationale.  When writing your own macro packages or documents that use
groff features and will be sourced by others with the .so request, you
may desire correct operation regardless of compatibility mode in the
surrounding context.  It may occur to you to save the existing value of
\n(.C into a temporary register, say, _C, at the beginning of your
file, turn compatibility mode off with .cp 0, then restore it from that
register at the end with the .cp request.  At the same time, a modular
design may lead you to multiple layers of inclusion.  You cannot use
the same register name everywhere or you risk “clobbering” the value
from an outer scope.  The two‐character register namespace of AT&T
troff is confining and mnemonically difficult; you may wish to use
groff's more capacious namespace.  However, attempting
             .nr _my_saved_C \n(.C
will not work in compatibility mode; the register name is too long.
“This is exactly what .do is for,” you think.
             .do nr _my_saved_C \n(.C
The foregoing will always save zero to your register, because .do turns
compatibility mode off.  It will not serve to have .do report the saved
compatibility mode in \n(.C for this special case, because the request
can do anything (except change compatibility mode in its enclosing
scope), including call macros and source files, and the value of \n(.C
must be reliable there.  What you need is:
             .do nr _my_saved_C \n[.cp]
             .cp 0
at the beginning of your file, followed by
             .cp _my_saved_C
at the end.

***

What remains to be done for this changeset:

* Port the documentation over to an appropriate place in the Texinfo
  manual.

To be done in a subsequent changeset:

* Change all the man pages and macro files that manipulate compatibilty
  mode to use the technique described.

See attached patch for the details.

What do you think?

Thank you for writing that regression test!  This was quite the
adventure.

Regards,
Branden
diff --git a/man/groff.7.man b/man/groff.7.man
index fc1c958d..bb244ab3 100644
--- a/man/groff.7.man
+++ b/man/groff.7.man
@@ -3885,6 +3885,13 @@ Current input line number.
 .REG .C
 1\~if compatibility mode is in effect, 0\~otherwise.
 .
+Always\~0 in a
+.request .do
+request;
+see
+.register .cp
+below.
+.
 .TPx
 .REG .cdp
 The depth of the last glyph added to the current environment.
@@ -3908,6 +3915,15 @@ It is positive if the glyph extends above the baseline.
 1\~if colors are enabled, 0\~otherwise.
 .
 .TPx
+.REG .cp
+Within a
+.request .do
+request,
+holds the saved value of compatibilty mode (see
+.register .C
+above).
+.
+.TPx
 .REG .csk
 The skew of the last glyph added to the current environment.
 .
diff --git a/man/groff_diff.7.man b/man/groff_diff.7.man
index 35658306..d406b790 100644
--- a/man/groff_diff.7.man
+++ b/man/groff_diff.7.man
@@ -2760,6 +2760,15 @@ It is positive if the glyph extends above the baseline.
 1\~if colors are enabled, 0\~otherwise.
 .
 .TP
+.B \[rs]n[.cp]
+Within a
+.B .do
+request,
+holds the saved value of compatibilty mode (see
+.B \[rs]n[.C]
+above).
+.
+.TP
 .B \[rs]n[.csk]
 The skew of the last glyph added to the current environment.
 .
@@ -3804,6 +3813,104 @@ escape sequence can be helpful in avoiding these escape sequences in
 names.
 .
 .P
+The register
+.B \[rs]n[.cp]
+is specialized and may require a statement of rationale.
+.
+When writing your own macro packages or documents that use
+.I groff
+features and will be sourced by others with the
+.B .so
+request,
+you may desire correct operation regardless of compatibility mode in the
+surrounding context.
+.
+It may occur to you to save the existing value of
+.B \[rs]n(.C
+into a temporary register,
+say,
+.BR _C ,
+at the beginning of your file,
+turn compatibility mode off with
+.BR .cp\~0 ,
+then restore it from that register at the end with the
+.B .cp
+request.
+.
+At the same time,
+a modular design may lead you to multiple layers of inclusion.
+.
+You cannot use the same register name everywhere or you risk
+\[lq]clobbering\[rq] the value from an outer scope.
+.
+The two-character register namespace of AT&T
+.I troff
+is confining and mnemonically difficult;
+you may wish to use
+.IR groff 's
+more capacious namespace.
+.
+However,
+attempting
+.RS
+.RS
+.EX
+\&.nr _my_saved_C \[rs]n(.C
+.EE
+.RE
+.RE
+will not work in compatibility mode;
+the register name is too long.
+.
+\[lq]This is exactly what
+.B .do
+is for,\[rq] you think.
+.RS
+.RS
+.EX
+\&.do nr _my_saved_C \[rs]n(.C
+.EE
+.RE
+.RE
+.
+The foregoing will always save zero to your register,
+because
+.B .do
+turns compatibility mode off.
+.
+It will not serve to have
+.B .do
+report the saved compatibility mode in
+.B \[rs]n(.C
+for this special case,
+because the request can do anything
+(except change compatibility mode in its enclosing scope),
+including call macros and source files,
+and the value of
+.B \[rs]n(.C
+must be reliable there.
+.
+What you need is:
+.RS
+.RS
+.EX
+\&.do nr _my_saved_C \[rs]n[.cp]
+\&.cp 0
+.EE
+.RE
+.RE
+at the beginning of your file,
+followed by
+.RS
+.RS
+.EX
+\&.cp _my_saved_C
+.EE
+.RE
+.RE
+at the end.
+.
+.P
 Fractional point sizes cause one noteworthy incompatibility.
 .
 In
diff --git a/src/roff/groff/groff.am b/src/roff/groff/groff.am
index a7999dc0..86f7acd5 100644
--- a/src/roff/groff/groff.am
+++ b/src/roff/groff/groff.am
@@ -41,6 +41,7 @@ groffoptsdir = $(libprogramdir)
 groffopts_DATA = $(GROFF_OPTS_OUTPUT)
 
 groff_TESTS = \
+  src/roff/groff/tests/dot-cp_register_works.sh \
   src/roff/groff/tests/on_latin1_device_oq_is_0x27.sh \
   src/roff/groff/tests/recognize_end_of_sentence.sh \
   src/roff/groff/tests/regression_savannah_56555.sh \
diff --git a/src/roff/groff/tests/dot-cp_register_works.sh b/src/roff/groff/tests/dot-cp_register_works.sh
new file mode 100755
index 00000000..2e3fee19
--- /dev/null
+++ b/src/roff/groff/tests/dot-cp_register_works.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+#
+# Copyright (C) 2020 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"
+
+DOC='.pl 1v
+A
+.do if 1 \n[.cp] \" Get initial compatibility state (depends on -C).
+B
+.do if 1 \n[.cp] \" Did observing the state change it?
+.cp 1
+C
+.do if 1 \n[.cp] \" Saved compatibility state should be 1 now.
+.cp 0
+D
+.do if 1 \n[.cp] \" Verify 1->0 transition.
+.cp 1
+E
+.do if 1 \n[.cp] \" Verify 0->1 transition.
+.cp 0
+F
+.if !\n[.C] \n[.cp] \" Outside of .do context, should return -1.
+'
+
+set -e
+
+printf "%s" "$DOC" | "$groff" -Tascii \
+    | grep -x "A 0 B 0 C 1 D 0 E 1 F -1"
+
+printf "%s" "$DOC" | "$groff" -C -Tascii \
+    | grep -x "A 1 B 1 C 1 D 0 E 1 F -1"
diff --git a/src/roff/troff/input.cpp b/src/roff/troff/input.cpp
index 26394ade..7d3b230c 100644
--- a/src/roff/troff/input.cpp
+++ b/src/roff/troff/input.cpp
@@ -103,6 +103,7 @@ static symbol end_macro_name;
 static symbol blank_line_macro_name;
 static symbol leading_spaces_macro_name;
 static int compatible_flag = 0;
+static int do_old_compatible_flag = -1;	// for .do request
 int ascii_output_flag = 0;
 int suppress_output_flag = 0;
 int is_html = 0;
@@ -2640,14 +2641,16 @@ static void trapping_blank_line()
 
 void do_request()
 {
-  int old_compatible_flag = compatible_flag;
+  assert(do_old_compatible_flag == -1);
+  do_old_compatible_flag = compatible_flag;
   compatible_flag = 0;
   symbol nm = get_name();
   if (nm.is_null())
     skip_line();
   else
     interpolate_macro(nm, 1);
-  compatible_flag = old_compatible_flag;
+  compatible_flag = do_old_compatible_flag;
+  do_old_compatible_flag = -1;
   request_or_macro *p = lookup_request(nm);
   macro *m = p->to_macro();
   if (m)
@@ -8297,6 +8300,7 @@ void init_input_requests()
   number_reg_dictionary.define(".$", new nargs_reg);
   number_reg_dictionary.define(".br", new break_flag_reg);
   number_reg_dictionary.define(".C", new constant_int_reg(&compatible_flag));
+  number_reg_dictionary.define(".cp", new constant_int_reg(&do_old_compatible_flag));
   number_reg_dictionary.define(".O", new variable_reg(&begin_level));
   number_reg_dictionary.define(".c", new lineno_reg);
   number_reg_dictionary.define(".color", new constant_int_reg(&color_flag));

Attachment: signature.asc
Description: PGP signature

Reply via email to