Does a package exist for programmatically (incrementally) constructing a macro

macrosparsing

When using LaTeX, I often find myself desiring a way to programmatically construct a macro using a series of instructions rather than in the usual "template" sense.

By "programmatically", I mean: rather than specifying the code for a macro directly, code snippets are incrementally appended to a buffer using a set of instructions, and then a final command is issued to "compile" the code into a new macro. The equivalent in most other languages would be string concatenation + an "eval" utility.

Ideally, three instructions would exist:

  • \AddExpandedToMacro{<code>}: fully expands <code> and appends to the macro under construction
  • \AddUnexpandedToMacro{<code>}: appends <code> to the macro under construction without expansion; <code> may contain command tokens (e.g., \foo) and ideally (if possible) balanced groups (e.g., {bar}) and balanced math mode shifts (e.g., $baz$)
  • \AddCharToMacro{<name>}: appends the special character specified by <name> to the macro under construction; <name> would be one of:

          bg – begin group ('{')
          eg – end group ('}')
          a – alignment ('&')
          p – parameter for macros ('#')
          c – command ('\')

The names and syntax of the instructions are simply suggestions. I would love, for example, the ability to simply write \AddUnexpandedToMacro{\foo{#1}} and have the string "\foo{#1}" appended to the macro under construction, but I suspect this isn't possible in LaTeX without messing with catcodes, which in turn creates all kinds of headaches re passing arguments.

The instructions would be sandwiched between:

  • \StartMacro{<name>}: fully expands <name> to a string and begins construction of the macro \<name>, flushing any previous in-scope construction with the same name
  • \BuildMacro{<name>} or \BuildMacro[<argcount>]{<name>}: acts in exactly* the same way as

           \newcommand {\<name>} [<argcount>] {<all appended code from instructions>}

    That is, when the command completes, a macro called \<name> will be defined in the local scope, accepting <argcount> parameters (or no parameters if [<argcount>] is omitted), and with "all appended code from instructions" interpreted exactly as it would be if it had been entered directly rather than constructed incrementally. (The behaviour of \BuildMacro would be undefined if the constructed code was unbalanced.)

          * as close as LaTeXically possible

The code for macros under construction would be stored either locally or globally. It doesn't matter.

Hence, for example:

\StartMacro{mymacro}
   \AddExpandedToMacro{[\ref{somelabel}]: }
   \AddUnexpandedToMacro{\niceand}
   \AddCharToMacro{c}
   \AddExpandedToMacro{fancy\roman{mycounter}}
      \AddCharToMacro{bg}
      \AddCharToMacro{p}
      \AddUnexpandedToMacro{1}
      \AddCharToMacro{eg}
\BuildMacro [1] {mymacro}

would, upon completion, have an identical effect to

\newcommand {\mymacro} [1] {[6.2]: \niceand\fancyiv{#1}}

assuming \ref{somelabel} expanded to 6.2 and mycounter was a counter with value 4.

As another example, the code:

\newcommand {\chapterdata} {lion/Lions of the Serengeti, dolphin/Dolphins of the Pacific}
\newcommand {\AddBracketed} [1]
   {\AddCharToMacro{bg}#1\AddCharToMacro{eg}}
\newcommand {\AddTitle} {\AddBracketed{\AddExpandedToMacro{\chaptitle}}}

\foreach \chapid/\chaptitle in \chapterdata {  % for each chapter
   \StartMacro{\chapid chaptitle}
      \ifx\smallcaptitles\undefined  % if not using \textsc
         \AddExpandedToMacro{\chaptitle}
      \else  % if using \textsc
         \AddUnexpandedToMacro{\texorpdfstring}
         \AddBracketed{%
            \AddUnexpandedToMacro{\textsc}%
            \AddTitle}
         \AddTitle
      \fi
   \BuildMacro{\chapid chaptitle}
}

would, upon completion, have an identical effect to

\newcommand {\lionchaptitle} {Lions of the Serengeti}
\newcommand {\dolphinchapter} {Dolphins of the Pacific}

if \smallcaptitles is undefined, or else to

\newcommand {\lionchaptitle} {\texorpdfstring{\textsc{Lions of the Serengeti}}{Lions of the Serengeti}}
\newcommand {\dolphinchaptitle} {\texorpdfstring{\textsc{Dolphins of the Pacific}}{Dolphins of the Pacific}}

if \smallcaptitles is defined.

(Incidentally, I realize that both examples can be achieved in other ways. I've made the examples simple for the sake of concision. The actual applications that I'm considering have the different "snippets" added in many different subroutines, loops, conditionals, etc. and are significantly more complicated.)

Features like the ability to define protected/global/etc. macros, the ability to define macros with optional arguments, a more diverse "instruction set" for appending commonly-used snippets to macros, etc., would all be nice to have, but not strictly necessary.

My questions are:

  1. Is a programmatic macro construction utility like this possible in LaTeX (no LuaTeX, unfortunately)?

  2. If yes, is there a package out there that provides this functionality or something very similar?

  3. If there is no package with this functionality, how monumental an effort would be required to implement this?

Best Answer

Not sure how this would be useful, but…

\documentclass{article}
\usepackage{refcount}

\ExplSyntaxOn

\NewDocumentCommand{\startmacro}{}
 {
  \tl_clear:N \l__coto_macro_body_tl
 }

\NewDocumentCommand{\finishmacro}{smm}
 {% #1 = *, #2 = macro name, #3 = number of arguments
  \IfBooleanTF{#1}
   {
    \coto_macro_finish:NNn \cs_set_protected:cn #2 { #3 }
   }
   {
    \coto_macro_finish:NNn \cs_set:cn #2 { #3 }
   }
 }

\NewDocumentCommand{\addexpanded}{m}
 {
  \tl_put_right:Nx \l__coto_macro_body_tl { #1 }
 }
\NewDocumentCommand{\addunexpanded}{m}
 {
  \tl_put_right:Nn \l__coto_macro_body_tl { \exp_not:n { #1 } }
 }
\NewDocumentCommand{\addcsname}{m}
 {
  \tl_put_right:Nx \l__coto_macro_body_tl { \exp_not:n { \exp_not:c { #1 } } }
 }
\NewDocumentCommand{\addleftbrace}{}
 {
  \tl_put_right:Nn \l__coto_macro_body_tl { \if_true: { \else: } \fi: }
 }
\NewDocumentCommand{\addrightbrace}{}
 {
  \tl_put_right:Nn \l__coto_macro_body_tl { \if_false: { \else: } \fi: }
 }
\NewDocumentCommand{\addparameter}{m}
 {
  \tl_put_right:Nn \l__coto_macro_body_tl { ## #1 }
 }

\tl_new:N \l__coto_macro_body_tl
\cs_generate_variant:Nn \tl_set:Nn { Ne }

\cs_new_protected:Nn \coto_macro_finish:NNn
 {
  \tl_set:Ne \l__coto_macro_body_tl { \l__coto_macro_body_tl }
  \exp_args:NnV #1 { __coto_macro_temp: \prg_replicate:nn { #3 } { n } } \l__coto_macro_body_tl
  \cs_new_eq:Nc #2 { __coto_macro_temp: \prg_replicate:nn { #3 } { n } }
 }

\ExplSyntaxOff

\begin{document}

\newcommand{\niceand}{NICEAND}
\newcommand{\fancyiv}[1]{FANCYIV-#1-}

\newcounter{mycounter}
\setcounter{mycounter}{4}

\setcounter{section}{6}
\setcounter{subsection}{1}
\refstepcounter{subsection}\label{somelabel}

\startmacro
\addexpanded{[\getrefnumber{somelabel}]: }
\addunexpanded{\niceand}
\addcsname{fancy\roman{mycounter}}
\addleftbrace
\addparameter{1}
\addrightbrace
\finishmacro{\mymacro}{1}

\texttt{\meaning\mymacro}

\mymacro{xyz}

\end{document}

With \finishmacro* the macro would be \protected.

enter image description here

Related Question