bugGNU roff - Bugs: bug #64212, [PATCH] [troff] want string-valued...

 
 

bug #64212: [PATCH] [troff] want string-valued register naming the next trap

Submitter:  G. Branden Robinson <gbranden>
Submitted:  Thu 18 May 2023 09:58:26 AM UTC
   
 
Category:  Core Severity:  1 - Wish
Item Group:  Feature change Status:  Fixed
Privacy:  Public Assigned to:  gbranden
Open/Closed:  Closed Planned Release:  1.24.0
* Mandatory Fields

Add a New Comment Rich Markup
   

Wed 09 Aug 2023 11:27:42 PM UTC, comment #5: 


commit 4c2cd5e076674b80135cf06fe6261e684f3a3cbd
Author: G. Branden Robinson <g.branden.robinson@gmail.com>
Date:   Wed Jul 26 02:58:27 2023 -0500

    [troff]: Add new `.trap` built-in register.

    * src/roff/troff/div.h (class diversion, class macro_diversion, class
      top_level_diversion): Declare new member function
      `get_next_trap_name`.

    * src/roff/troff/div.cpp (macro_diversion::get_next_trap_name): New
      member function returns name of diversion trap if any, and if its
      position is greater than the vertical drawing position, otherwise an
      empty string.

      (top_level_diversion::get_next_trap_name): New member function returns
      the name of the next vertical position trap, if any, otherwise an
      empty string.

      (class next_trap_name_reg): New class has one member, a `get_string()`
      function.

      (next_trap_name_reg::get_string): New function.

      (init_div_requests): Add `.trap` to the register dictionary and wire
      it up to `next_trap_name_reg`.

    * doc/groff.texi (Page Location Traps):
    * man/groff.7.man (Read-only registers):
    * man/groff_diff.7.man (New registers): Document it.

    * src/roff/groff/tests/dot-trap_register_works.sh: Test it.
    * src/roff/groff/groff.am (groff_TESTS): Run test.

    * NEWS: Add item.

    Fixes <https://savannah.gnu.org/bugs/?64212>.  Thanks to Dave Kemper for
    feedback.


G. Branden Robinson <gbranden>
Group administrator
Wed 26 Jul 2023 08:03:18 AM UTC, comment #4: 

I think I've got this fleshed out.

Attached.

(file #54972)

G. Branden Robinson <gbranden>
Group administrator
Fri 19 May 2023 03:37:09 AM UTC, comment #3: 

comment #2:

> Sorry, I wasn't clear about this; the internal page-bottom
> trap always exists, but it has no name.


...and indeed, isn't called a trap in the docs, so I hadn't thought of it as a trap.  While, per your analysis, it does have some trap-like properties, it lacks others, so it's not immediately clear to me whether it should be considered a trap with special properties or (as the docs currently treat it) a unique object with its own properties.  (Though I can guess which interpretation Admiral Ackbar would support.)

> It's conceivable that a full-service macro package will not
> set up a page-bottom trap if no footer is configured and no
> footnotes occur.


Well, even without a footer or footnotes, you almost never want text running all the way to the page bottom, so I assumed all the standard packages do by default set a bottom margin, which entails a trap.

Dave <barx>
Group Member
Thu 18 May 2023 09:51:01 PM UTC, comment #2: 

comment #1:

> original submission:
> > if, somehow, there is no next trap at the top level, but
> > that...should be impossible?
>
> The info documentation for \n[.t] thinks it's possible: "If there are no traps between the current position and the bottom of the page, it contains the distance to the page bottom."


Right.  Sorry, I wasn't clear about this; the internal page-bottom trap always exists, but it has no name.

Consider the following.


$ cat ./EXPERIMENTS/dot-t-within-trap.groff
.de TT
.tm trap: $0=\\$0, .t=\\n(.t, .trap=\\n[.trap]
..
.wh 11i TT
Hello, world!
.tm body1: .t=\n(.t, .trap=\n[.trap]
.br
.tm body2: .t=\n(.t, .trap=\n[.trap]
$ ./build/test-groff -Tascii ./EXPERIMENTS/dot-t-within-trap.groff | cat -s
body1: .t=2640, .trap=
body2: .t=2600, .trap=
Hello, world!


The TT trap was never sprung!

Now, let's change the placement of the trap from 11i (the bottom of a U.S. letter page in portrait orientation, and the formatter default) to 10i.


$ ./build/test-groff -Tascii ./EXPERIMENTS/dot-t-within-trap.groff | cat -s
body1: .t=2400, .trap=TT
body2: .t=2360, .trap=TT
trap: $0=TT, .t=240, .trap=
Hello, world!


Consider what must have happened: our trap was planted, and in fact if we move it back to 11i and invoke `ptr` we can see it.


$ ./build/test-groff -Tascii ./EXPERIMENTS/dot-t-within-trap.groff | cat -s
TT      2640
body1: .t=2640, .trap=
body2: .t=2600, .trap=
Hello, world!


The internal trap is not reported, because it has no name.  But it's there--and, if our documentation is accurate, it is at 2640 units, before the TT trap.  That is why \n[.trap] interpolates an empty value.


 -- Request: .wh dist [name]
     Call macro NAME when the vertical position DIST on the page is
     reached or passed in the downward direction.  The default scaling
     unit is 'v'.  Non-negative values for DIST set the trap relative to
     the top of the page; negative values set the trap relative to the
     bottom of the page.  An existing _visible_ trap (see below) at DIST
     is removed; this is 'wh''s sole function if NAME is missing.

     A trap is sprung only if it is "visible", meaning that its location
     is reachable on the page(1) (*note Page Location
     Traps-Footnote-1::) and it is not hidden by another trap at the
     same location already planted there.
[...]
     It is possible to have more than one trap at the same location
     (although only one at a time can be visible); to achieve this, the
     traps must be defined at different locations, then moved to the
     same place with the 'ch' request.
[...]
 -- Register: \n[.t]
     The read-only register '.t' holds the distance to the next vertical
     position trap.  If there are no traps between the current position
     and the bottom of the page, it contains the distance to the page
     bottom.  Within a diversion, in the absence of a diversion trap,
     this distance is the largest representable integer in basic
     units--effectively infinite.

 -- Request: .ch name [dist]
     Change the location of a trap by moving macro NAME to new location
     DIST, or by unplanting it altogether if DIST is absent.  The
     default scaling unit is 'v'.  Parameters to 'ch' are specified in
     the opposite order from 'wh'.  If NAME is the earliest planted
     macro of multiple traps at the same location, (re)moving it from
     that location exposes the macro next least recently planted at the
     same place.(2)  (*note Page Location Traps-Footnote-2::)
[...]
   (1) A trap planted at '20i' or '-30i' will not be sprung on a page of
length '11i'.

   (2) It may help to think of each trap location as maintaining a
queue; 'wh' operates on the head of the queue, and 'ch' operates on its
tail.  Only the trap at the head of the queue is visible.


I am beginning to think I should start discussing the unnamed internal trap more explicitly.  I still have a hangover from some earlier documentation applying the term "pseudo" to it.  It seems clearer to me now that there truly is nothing ersatz about it.  It exists and behaves like a named trap in most respects, except insofar as it doesn't have a name.

In fact, the unnamed/implicit/internal trap has a superpower; recall this in the description of `wh`.

"An existing visible trap (see below) at DIST is removed;"

...which doesn't apply to the implicit trap.  You can shove a named trap in *front* of it with `wh`, but it will not be deleted.

Observe:


$ cat EXPERIMENTS/expose-implicit-trap.roff
.de TT
.tm I'm a happy trapper on page \\n%.
..
.wh 11i TT
Hello, world!
.bp
.ch TT 10i
Page two.
.bp
Page three.
$ ./build/test-groff -Tascii EXPERIMENTS/expose-implicit-trap.roff | cat -s
I'm a happy trapper on page 2.
I'm a happy trapper on page 3.
Hello, world!

Page two.

Page three.

 

> In all the standard macro packages, it's likely impossible unless the user goes to some pains to remove the bottom-margin trap.


It's conceivable that a full-service macro package will not set up a page-bottom trap if no footer is configured and no footnotes occur.  I haven't verified if any of the ones we ship are so parsimonious, however.

> > Does anybody want this?
>
> I don't see any harm in adding it even if only people who are debugging groff internals ever end up using it.


Okay.  I'll ask the list.

The funny situation I ran across is this:

What should the value of \n[.t] (and, for that matter, the proposed \[.trap]) be when you're already executing a trap?

As we saw above, when we instrument a trap to report next-trap information, we got this:


trap: $0=TT, .t=240, .trap=


groff has dutifully updated `\$0` for us, a nice feature even though traps can't take arguments (and especially helpful since trap-called macros, like others, can be aliased).  \n[.trap] is empty, and since we're not in a diversion, does this mean that the next trap is the implicit trap?

Well, there are a couple of ways to find out.  One is to plant another trap.


$ cat EXPERIMENTS/two-traps.groff
.de TT
.tm TT trap: $0=\\$0, .t=\\n(.t, .trap=\\n[.trap]
..
.de UU
.tm UU trap: $0=\\$0, .t=\\n(.t, .trap=\\n[.trap]
..
.wh 10i TT
.wh 10.5i UU
Hello, world!
$ ./build/test-groff -Tascii EXPERIMENTS/two-traps.groff|cat -s
TT trap: $0=TT, .t=120, .trap=UU
UU trap: $0=UU, .t=120, .trap=
Hello, world!


Lovely!

Another is to rudely invade the macro/string/diversion name space and give the unnamed trap a name, at least according to this proposed '\n[.trap]' feature.


+const char *top_level_diversion::get_next_trap_name()
+{
+  vunits next_trap_pos;
+  trap *next_trap = find_next_trap(&next_trap_pos);
+  if (0 /* nullptr */ == next_trap)
+    return "GBR-NO-NEXT-TOP-LEVEL-TRAP";
+  else
+    return next_trap->nm.contents();
+}
+
$ (cd build && make -j troff)
$ ./build/test-groff -Tascii EXPERIMENTS/two-traps.groff|cat -s
TT trap: $0=TT, .t=120, .trap=UU
UU trap: $0=UU, .t=120, .trap=GBR-NO-NEXT-TOP-LEVEL-TRAP
Hello, world!


As can be seen, what I was really dealing with was a null pointer.  The implicit trap is neither wave nor particle.  Formatter logic will arrange for the page bottom to eventually be reached, no matter what.  (In groff, vertical position traps can be disabled, which as our manual notes, means that the current page "can't be ejected".)

So I stuck a ".vpt 0" at the end of "two-traps.groff" above, and got this:


$ ./build/test-groff -Tascii EXPERIMENTS/two-traps.groff|cat -s
troff: error: can't continue page ejection because vertical position traps disabled
troff: error: can't continue page ejection because vertical position traps disabled
Hello, world!


The formatter manages to wrangle itself free of this situation and exit rather than looping infinitely.  (I wonder why the diagnostic is thrown twice...)

So there would seem to be a lot of special casing going on here to stumble forward with invalid input documents.

Maybe '\n[.trap]', plus a more explicit exploration of the implicit trap in our documentation, will help people.

G. Branden Robinson <gbranden>
Group administrator
Thu 18 May 2023 08:16:38 PM UTC, comment #1: 

original submission:

> if, somehow, there is no next trap at the top level, but
> that...should be impossible?


The info documentation for \n[.t] thinks it's possible: "If there are no traps between the current position and the bottom of the page, it contains the distance to the page bottom."

In all the standard macro packages, it's likely impossible unless the user goes to some pains to remove the bottom-margin trap.

> Does anybody want this?


I don't see any harm in adding it even if only people who are debugging groff internals ever end up using it.

Dave <barx>
Group Member
Thu 18 May 2023 09:58:26 AM UTC, original submission:  

This was, uh, "inspired" by the ongoing headache that is bug #57538.

I found it annoying that you can get the distance to the next trap but not its name, when internally, the formatter has ready access to this information.

So here's a patch adding a new read-only, string-valued register that tells you the name of the next trap you will spring if you are in the top level.

An empty string is interpolated if you are in a diversion (there is only at most one trap in a diversion; it has no name) or if, somehow, there is no next trap at the top level, but that...should be impossible?

Anyway, it worked to tell me what I wanted.  It didn't cause a Eureka moment, though.


diff --git a/src/roff/troff/div.cpp b/src/roff/troff/div.cpp
index f4dc06f33..679575f99 100644
--- a/src/roff/troff/div.cpp
+++ b/src/roff/troff/div.cpp
@@ -250,6 +250,11 @@ vunits macro_diversion::distance_to_next_trap()
     return vunits(INT_MAX - vresolution);
 }

+const char *macro_diversion::get_next_trap_name()
+{
+  return "";
+}
+
 void macro_diversion::transparent_output(unsigned char c)
 {
   mac->append(c);
@@ -372,6 +377,16 @@ vunits top_level_diversion::distance_to_next_trap()
     return d - vertical_position;
 }

+const char *top_level_diversion::get_next_trap_name()
+{
+  vunits next_trap_pos;
+  trap *next_trap = find_next_trap(&next_trap_pos);
+  if (0 /* nullptr */ == next_trap)
+    return "";
+  else
+    return next_trap->nm.contents();
+}
+
 void top_level_diversion::output(node *nd, int retain_size,
                                 vunits vs, vunits post_vs, hunits width)
 {
@@ -1059,6 +1074,16 @@ const char *diversion_name_reg::get_string()
   return curdiv->get_diversion_name();
 }

+class next_trap_name_reg : public reg {
+public:
+  const char *get_string();
+};
+
+const char *next_trap_name_reg::get_string()
+{
+  return curdiv->get_next_trap_name();
+}
+
 class page_number_reg : public general_reg {
 public:
   page_number_reg();
@@ -1194,6 +1219,7 @@ void init_div_requests()
   register_dictionary.define(".pe", new page_ejecting_reg);
   register_dictionary.define(".pn", new next_page_number_reg);
   register_dictionary.define(".t", new distance_to_next_trap_reg);
+  register_dictionary.define(".trap", new next_trap_name_reg);
   register_dictionary.define(".trunc",
       new constant_vunits_reg(&truncated_space));
   register_dictionary.define(".vpt",
diff --git a/src/roff/troff/div.h b/src/roff/troff/div.h
index 15c7807a5..6180caa0b 100644
--- a/src/roff/troff/div.h
+++ b/src/roff/troff/div.h
@@ -57,6 +57,7 @@ public:
   vunits get_vertical_position() { return vertical_position; }
   vunits get_high_water_mark() { return high_water_mark; }
   virtual vunits distance_to_next_trap() = 0;
+  virtual const char * get_next_trap_name() = 0;
   void need(vunits);
   const char *get_diversion_name() { return nm.contents(); }
   virtual void set_diversion_trap(symbol, vunits) = 0;
@@ -84,6 +85,7 @@ public:
   void vjustify(symbol);
 #endif /* COLUMN */
   vunits distance_to_next_trap();
+  const char *get_next_trap_name();
   void set_diversion_trap(symbol, vunits);
   void clear_diversion_trap();
   void copy_file(const char *filename);
@@ -125,6 +127,7 @@ public:
   hunits get_page_offset() { return page_offset; }
   vunits get_page_length() { return page_length; }
   vunits distance_to_next_trap();
+  const char *get_next_trap_name();
   void add_trap(symbol nm, vunits pos);
   void change_trap(symbol nm, vunits pos);
   void remove_trap(symbol);


Does anybody want this?

G. Branden Robinson <gbranden>
Group administrator

 

(Note: upload size limit is set to 16384 kB, after insertion of the required escape characters.)

Attach Files:
   
   
Comment:
   

Attached Files
file #54972:  64212.diff added by gbranden (10KiB - text/x-patch)

 

Depends on the following items: None found

Items that depend on this one: None found

 

Carbon-Copy List
  • -email is unavailable- added by barx (Posted a comment)
  • -email is unavailable- added by gbranden (Submitted the item)
  •  

    There are 0 votes so far. Votes easily highlight which items people would like to see resolved in priority, independently of the priority of the item set by tracker managers.

    Only logged-in users can vote.

     

    Follow 7 latest changes.

    Date Changed by Updated Field Previous Value => Replaced by
    2023-08-09 gbranden StatusIn Progress Fixed
        Open/ClosedOpen Closed
        Planned ReleaseNone 1.24.0
    2023-07-26 gbranden Attached File- Added 64212.diff, #54972
    2023-07-26 gbranden StatusNeed Info In Progress
        Assigned toNone gbranden
    2023-06-23 gbranden Summary[PATCH] give troff a string register naming the next trap [PATCH] [troff] want string-valued register naming the next trap

    Back to the top

    Powered by Savane 3.13-3230.
    Corresponding source code