Hi Branden,

G. Branden Robinson wrote on Wed, Oct 29, 2025 at 06:16:04AM -0500:

> I've root-caused the problem; expect a fix in my next push.

Thanks for investigating and fixing.

[...]
> The problem is not that a `.T` string
> exists, but that a `.T` _register_ _also_ exists.
> 
> commit 897acc5fcd06ee91127185619d33bd90a22dee6e
[,,,]
>     [mdoc]: Fix Savannah #67646 (`.T` as macro arg).
[...]
>     mandoc(1) does not exhibit this problem, likely because it
>     lacks a *roff formatter that defines both a `.T` register and
>     a `.T` string; rather it simulates both of these.

This last sentence is misleading in two very minor ways:

 1. In mandoc, the roff *formatters* (roff_term.c, roff_html.c)
    do not "simulate" registers or strings in any way; instead,
    the roff formatters are (by design) completely unrelated to
    both registers and strings, completely unaware that such
    concepts even exist.

 2. In mandoc, the roff *parsers* (roff.c, roff_escape.c)
    predefine both the .T string and the .T register (rather than
    somehow "simulating" them) because both are user-visible features
    of the roff(7) language.  Both are even documented in the
    roff(7) manual distributed with mandoc:

      ESCAPE SEQUENCE REFERENCE
      [...]
        \*[name]
           Interpolate the string with the name.  For short names,
           there are variants \*c and \*(cc.
           One string is predefined on the roff language level: \*(.T
           expands to the name of the output device, for example ascii,
           utf8, ps, pdf, html, or markdown.
      [...]
      NUMBER REGISTER REFERENCE
      [...]
        .T Whether an output device has been selected; mandoc(1) always
           returns 1, meaning yes.

The real point why mandoc does not suffer from this former 
mdoc-macroset bug is that to distinguish callable macro names
from plain-text arguments, it does not use roff(7) registers (such
use being an internal implementation detail of mdoc macro sets
that is apparently prone to clashes), but instead uses a separate
hash table of macro names that cannot clash with user-visible roff(7)
language features.

Not a big deal because this is only a slightly misleading commit
message, which by definition doesn't hurt users.

I'm not testing your fix right now, but i'm doing some code review -
not very deep, just mentioning what *immediately* springs to the eye:

> diff --git a/tmac/doc.tmac b/tmac/doc.tmac
> index 47064f9c6..6f61f45d8 100644
> --- a/tmac/doc.tmac
> +++ b/tmac/doc.tmac
> @@ -2644,10 +2644,17 @@ .de doc-get-arg-type*

Does the .doc-get-arg-type internal macro right above suffer from a
similar issue, or is that macro only used in contexts where treating
the string ".T" as type 1 causes no harm?  On first sight, i'm
not sure what is going on, but the code of both macros looks
suspiciously similar.

>  .      if r doc-punct\*[doc-arg\$1] \
>  .        nr doc-arg-type \n[doc-punct\*[doc-arg\$1]]
>  .    \}
> -.    el \
> -.      if r \*[doc-arg\$1] \
> -.        if d \*[doc-arg\$1] \
> -.          nr doc-arg-type 1
> +.    el \{\
> +.      \" Handle the *roff internal string '.T' specially; it is defined
> +.      \" in the formatter, which _also_ defines a '.T' _register_,
> +.      \" colliding perfectly with mdoc's argument type system.
> +.      ie !'\?\*[doc-arg\$1]\?'\?.T\?' \
> +.        if r \*[doc-arg\$1] \
> +.          if d \*[doc-arg\$1] \
> +.            nr doc-arg-type 1
> +.      el \
> +.        nr doc-arg-type 2

This else clause looks pointless to me.
The same .nr request is also present as the default,
right above just after the introductory .de request.

> +.    \}
>  .  \}
>  ..
>  .ec

Otherwise, on first sight and without investigating deeply,
your fix makes sense to me.

Yours,
  Ingo

Reply via email to