I'm trying to understand the exact semantics of @@ replacement in the l3doc class. What I gathered so far from the documentation is the following:

  • No replacement happens before the %<@@=name> declaration occured in the document.
  • When a replacement happens, the following patterns are tried and replaced in order on the character sequence (from left to right):

    "@@@@" -> "@@"
    "__@@" -> "__" + name
    "_@@"  -> "__" + name
    "@@"   -> "__" + name
  • Replacement happens in the arguments of \cmd and \cs, unless replace=false was defined in the optional argument of these commands.
  • Replacement happens in all control sequence names listed in the first argument of the function and macro environments.
  • Replacement happens in all lines between \begin{macrocode} and \end{macrocode}, unless the character sequence <@@= occurs in that line.

My questions:

  1. Are there any other places where a replacement can happen?
  2. Are there any other options, global or local ones, that have an influence on the replacment?
  3. What is the expected replacement behavior if several %<@@=name> declarations occur in the same document?

1 Answer 1


There are two instances when the replacement occurs. The first is the docstrip step, when the source is read and the code from the source .dtx is copied to the target .sty/.cls file, and the second when the code is read once again to typeset the documentation and implementation.

In the docstrip step the l3docstrip.tex code is used, which patches the \moduleOption from the original docstrip.tex to include the possibility of the %<@@=...> syntax. Here docstrip.tex reads the input file line by line, and if a %<@@=...> is found the program sets the active module name and for all following lines the replacement is made using \replaceModuleInLine (which otherwise would be an empty definition). Any number of %<@@=...> might be used and the code will just change to the new module name. Here the replacement always happen, regardless of any settings, as long as a %<@@=...> appeared and \replaceModuleInLine was not cleared.

In the typesetting step, the command that does the replacement is \__codedoc_replace_at_at:N. The first condition is that the replacement is only done if \g__codedoc_module_name_tl is not empty, that is, a %<@@=name> was given, as you already said. This is the only replacement condition applied in the replacement macro itself, so it's the “most global” condition. The other ones are applied depending on the context.

The other appearances of \__codedoc_replace_at_at:N are in:

  • \__codedoc_cmd:nn, which is used by \cmd, \cs, and \tn, and will only happen if \l__codedoc_cmd_replace_bool (that is, the replace option) is true. In \tn the option replace is false by default, so you need \tn[replace=true] to trigger the replacement.

  • \__codedoc_names_get_seq:nN, which is used by both \__codedoc_function:nnw and \__codedoc_macro:nnw which, in turn, are used by the variable, function, and macro environments. The replacement occurs in the mandatory argument of these environments, which is the list of variables/functions/macros being described. In any of these, the replacement occurs if \l__codedoc_names_verb_bool (that is, the verb option) is set to false, so \begin{macro}[verb]{\@@_something:n} will not replace.

  • \__codedoc_xmacro_code:n which is used by the macrocode environment. In this environment the replacement always happens (given %<@@=...> was used somewhere), but there are two different behaviours here. The first one is if there is no %<@@=....> inside the environment, in which case the replacement happens normally. The second one is if there are %<@@=...> in the environment. In this case the code up to the %<@@=...> is grabbed, all @@ are replaced with the current module name, the module name is updated, and it proceeds scanning the code for the next %<@@=...> or the end of the environment.
    Until recently the code would break if more than one %<@@=...> was found in a single macrocode environment (arbitrarily many could still appear in separate environments). This was fixed (97daab8) and newer versions of l3doc (later than June 27, 2019) should have the same replacement behaviour as l3docstrip.

For versions of l3doc older than June 27, 2019:

When a macrocode environment contains a %<@@=...> it will globally change the value of \g__codedoc_module_name_tl, and from that point on the new value will be used, so you can have arbitrarily many %<@@=....> in a document. For example, this source (supposing no %<@@=...> was used before):

%    \begin{macrocode}
\cs_new:Npn \@@_world:n {}
\cs_new:Npn \@@_world:n {}
%    \end{macrocode}

becomes this code:

\cs_new:Npn \@@_world:n {}
\cs_new:Npn \hello_world:n {}

However only one usage of %<@@=...> is expected per macrocode environment, so this source:

%    \begin{macrocode}
\cs_new:Npn \@@_world:n {}
\cs_new:Npn \@@_world:n {}
\cs_new:Npn \@@_world:n {}
%    \end{macrocode}

breaks :-)

! Missing { inserted.
<to be read again> 
l.228 %    \end{macrocode}


because the code replaces the @@ in %<@@=goodbye> by __hello, not knowing that it's another change of name space, and chaos ensues. A slight change in \__codedoc_xmacro_code:n and \__codedoc_xmacro_code:w makes the behaviour of l3cls the same as l3docstrip regarding the replacement of @@ (put in your preamble):

  \char_set_catcode_letter:N \@
  \char_set_catcode_active:N \<
  \char_set_catcode_active:N \>
  \cs_gset_protected:Npn \__codedoc_xmacro_code:n #1
      \tl_clear:N \l__codedoc_tmpa_tl
      \tl_if_in:nnTF {#1} { < @ @ = }
        { \__codedoc_xmacro_code:w #1 < @ @ = \q_recursion_tail > \q_recursion_stop }
          \tl_set:Nn \l__codedoc_tmpa_tl {#1}
          \__codedoc_detect_internals:N \l__codedoc_tmpa_tl
          \__codedoc_replace_at_at:N \l__codedoc_tmpa_tl
          \tl_use:N \l__codedoc_tmpa_tl
  \cs_gset_protected:Npn \__codedoc_xmacro_code:w #1 < @ @ = #2 >
      \tl_set:Nn \l__codedoc_tmpb_tl {#1}
      \__codedoc_detect_internals:N \l__codedoc_tmpb_tl
      \__codedoc_replace_at_at:N \l__codedoc_tmpb_tl
      \tl_put_right:NV \l__codedoc_tmpa_tl \l__codedoc_tmpb_tl
      \quark_if_recursion_tail_stop_do:nn {#2}
        { \tl_use:N \l__codedoc_tmpa_tl }
      \tl_gset:Nn \g__codedoc_module_name_tl {#2}
      \tl_put_right:Nn \l__codedoc_tmpa_tl { < \text { \verbatim@font @ @ = #2 } > }

The code above makes it possible to have arbitrarily many %<@@=...> in a macrocode environment.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .