7

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?

6
  • 1
    there is no need to "build" a macro. You can simply append e.g. with \tl_put_right:Nn Commented Aug 24, 2022 at 19:24
  • it might be worthy learning the hook system of the latest few releases of the LaTeX kernel. I've found myself replacing 5 or 6 uses of etoolbox's \pretocmd-and-friends with \AddToHook Commented Aug 25, 2022 at 2:40
  • So many assignments :(
    – Joseph Wright
    Commented Aug 25, 2022 at 7:17
  • 1
    I kind of covered this (tricks to handle unbalanced token list in TeX, by putting something that x-expands to the desired token list) in tex.stackexchange.com/a/638453/250119, but maybe also see other answers under there. But "appending" a \ does require manipulating the whole thing as a string and \scantokens the result, it may be a better idea to learn how tokens work in TeX and manipulate the input as a token list directly instead of a string. ■ Side note, \tl_put_right:Nn in a loop has quadratic time complexity, use \tl_build_put_right:Nn etc. for performance.
    – user202729
    Commented Aug 26, 2022 at 0:11
  • 1
    A bit odd many people don't see the usefulness of this, while it's used in e.g. expl3 source code e.g. in \__regex_replacement_put:n to allow "appending" e.g. unbalanced { or } to a "token list". They think other people don't want to write complex macros?
    – user202729
    Commented Aug 26, 2022 at 0:21

5 Answers 5

6

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

8
  • Is the \exp_not:n in \addexpanded supposed to be there?
    – COTO
    Commented Aug 24, 2022 at 23:30
  • @COTO yes, it is, since the assignment done is \tl_put_right:Nx instead of \tl_put_right:Nn, the x means that it is fully expanded there. The effect is the same as using \tl_put_right:Nn \l__coto_macro_body_tl {#1} (and I'd personally prefer the latter for clarity, same goes for \addcsname, there I'd personally prefer \tl_put_right:Nn \l__coto_macro_body_tl { \exp_not:c {#1} } -- but it really makes no difference so both is fine and just a matter of taste).
    – Skillmon
    Commented Aug 25, 2022 at 6:25
  • @COTO No, it wasn't. I also changed \addparameter, since I find it strange to add # and then 1: they're a single unit, when thinking about macros.
    – egreg
    Commented Aug 25, 2022 at 7:14
  • 1
    Did you consider using \tl_build?
    – Joseph Wright
    Commented Aug 25, 2022 at 7:23
  • @JosephWright Yes, but it wouldn't be really very different. Possibly a bit more efficient. Anyway, since I can't recommend doing the job like this…
    – egreg
    Commented Aug 25, 2022 at 7:28
5

Not sure how this would be useful, but it can be simply implemented only by TeX primitives, no Expl3 is needed:

\def\addto #1#2{\expandafter\def\expandafter#1\expandafter{#1#2}}
\def\p{##}
\def\b{{\iffalse}\fi}
\def\e{\iffalse{\fi}}

\def\startmacro{\def\tmp{}}
\def\addexpanded#1{\addto\tmp{#1}}
\def\addunexpanded#1{\addto\tmp{\unexpanded{#1}}}
\def\addcsname#1{\addto\tmp{\expandafter\noexpand\csname#1\endcsname}}
\def\addleftbrace{\addto\tmp{\b}}
\def\addrightbrace{\addto\tmp{\e}}
\def\addparameter#1{\addto\tmp{\p#1}}
\def\finishmacro#1#{\toks0={#1}\finishmacroA}
\def\finishmacroA#1{\expandafter\edef\csname#1\expandafter\endcsname\the\toks0{\tmp}}

%test:
\def\reference{[6.1]}
\newcount\counter  \counter=13

\startmacro
  \addexpanded{\reference}
  \addunexpanded{\niceand}
  \addcsname{fancy\the\counter}
  \addleftbrace
  \addparameter 1
  \addrightbrace
\finishmacro #1{mymacro}

{\tt \meaning\mymacro}
\bye
4

The most flexible approach for such functionality would be expl3 where you can manipulate token lists, as Ulrike mentioned in a comment and egreg demonstrated in his answer.

However, as a proof of concept here an implementation of the first example with the etoolbox package. This package provides the \apptocmd macro, which can be used to append code at the end of an existing command. The idea therefore is that you start with an empty command, and add the pieces throughout the document.

There are some limitations, mostly that each piece must be balanced and well-formed. Therefore you cannot add a single { and add the corresponding } later, or add the # at one point and the 1 afterwards. Of course \bgroup and \egroup themselves are allowed in isolation, since they are separate macros.

For creating a command name like \fancyiv out of different pieces you can use \csname ... \endcsname, which come close to the string concatenation + eval principle, but only for building a macro name.

For expansion a relatively easy way is to use the list data structure offered by etoolbox. This allows to use the macros \listadd for unexpanded contents and \listeadd for expanded contents. Then at the end you can loop the list and add each list item to the macro using \apptocmd. However, unfortunately you cannot store parameters such as #1 in a list easily. Therefore these should be added with separate \apptocmd calls outside of the loop. Another option is to use separate \apptocmd calls instead of \listadd and use the list only for contents that need to be expanded.

Note that \ref is a special case: during the first LaTeX run, when the number is not yet know, you cannot expand \ref using \edef and by extension also not with \listeadd. In the second run \ref is expandable but of course you don't get to the second run if the first run is aborted because of an error. You can however use \getrefnumber from the refcount package, which expands to 0 on the first run and the correct number on the following runs.

MWE:

\documentclass{article}
\usepackage[T1]{fontenc} % ensure correct output of \meaning
\usepackage{refcount} % expansion of \ref
\usepackage{etoolbox} % build macros
\newcounter{mycounter}
\newcommand{\fancyiv}[1]{fancy 4: #1} % dummy helper macro
\begin{document}
\setcounter{section}{6}
\setcounter{subsection}{1}
\subsection{Building a macro}
\label{somelabel}
% AddExpandedToMacro
\listeadd{\macroparts}{[\getrefnumber{somelabel}]}
% AddUnexpandedToMacro
\listadd{\macroparts}{\LaTeX\ }
\setcounter{mycounter}{4}
% Construct command name
\listadd{\macroparts}{\csname fancy\roman{mycounter}\endcsname}
% BuildMacro step 1: initialize empty macro
\newcommand{\mymacro}[1]{}
% BuildMacro step 2: initialize list processing
\renewcommand*{\do}[1]{\apptocmd{\mymacro}{#1}{\typeout{adding #1 ok}}{\typeout{adding #1 failed}}}
% BuildMacro step 3: execute list processing
\dolistloop{\macroparts}
% Workaround for arguments: directly append {#1} outside of the loop
\apptocmd{\mymacro}{{#1}}{\typeout{argument ok}}{\typeout{argument failed}}
% Results
Macro code:\\\small\meaning\mymacro\\[1Em]
\normalsize Result: \mymacro{hi}
\end{document}

enter image description here

Of course this proof of concept is very simple and has some problems, but it addresses to some extend the question "is there a package that provides this functionality or something similar".

One final note: the question states that "I often find myself desiring a way to programmatically construct a macro". While it is interesting to see how far you can get with this, it is rather unusual to take this approach to macro writing in LaTeX. It may be a symptom of being experienced in another programming language or paradigm and trying to apply this in LaTeX. However, this may not be the best approach in this case. In general it is usually best not to define too many of your own macros when you write a document, and stick to the tools that LaTeX provides for you - in other words, try not to make things unnecessarily complicated :)

1
  • 1
    "It may be a symptom of being experienced in another programming language or paradigm and trying to apply this in LaTeX.": This is a fair point. However, the contexts where I've found myself wanting this are usually those where the "LaTeX way of doing things" (as far as I can tell from looking at library code, etc.) is long and unholy chains of \expandafters. If I was going to program a library, I'd definitely do this instead of rely on this kind of utility for the sake of efficiency. But for little one-off tweaks and features, this kind of ultimate versatility is very attractive.
    – COTO
    Commented Aug 24, 2022 at 23:26
3

The following sets up a single macro with a key=value interface in which the keys function as your suggested macros (with one or two added).

The entire macro building process is done by expansion (so each of the used keys is itself expandable -- this doesn't cover the options).

Using the cont-option you can continue (add to) a previously defined macro, and with pre you can put stuff infront of the definition of a previously defined macro, it is important to have the same args-count in both cases though.

\documentclass{article}

\usepackage{refcount}
\usepackage{expkv-def}

\makeatletter
\ekvsetdef\cmdconstructorOptions{cmdconstructor/options}
\ekvdefinekeys{cmdconstructor/options}
  {
     boolTF glob = \cmdconstructor@glob
    ,boolTF prot = \cmdconstructor@prot
    ,boolTF long = \cmdconstructor@long
    ,boolTF cont = \cmdconstructor@cont
    ,boolTF pre  = \cmdconstructor@pre
    ,eint   args = \cmdconstructor@args
    ,also code args =
      \ifnum9<\cmdconstructor@args
        \GenericError{cmdconstructor}{Too many arguments.}{}{}%
        \cmdconstructor@args=9
      \else
        \ifnum\z@>\cmdconstructor@args
          \GenericError{cmdconstructor}{Too few arguments.}{}{}%
          \cmdconstructor@args=\z@
        \fi
      \fi
  }
\NewDocumentCommand \cmdconstructor { m O{} +m }
  {%
    \begingroup
      \cmdconstructorOptions{#2}%
      \expandafter\cmdconstructor@argspec\the\cmdconstructor@args
      \edef\cmdconstructor@tmp
        {%
          {%
            \cmdconstructor@glob{\global}{}%
            \cmdconstructor@prot{\protected}{}%
            \cmdconstructor@long{\long}{}%
          }%
          {\unexpanded\expandafter{\cmdconstructor@tmp}}%
          {%
            \cmdconstructor@cont
              {%
                \unexpanded{\unexpanded\expandafter}%
                  {\unexpanded{#1}\cmdconstructor@arglist1}%
              }%
              {}%
          }%
          {%
            \cmdconstructor@pre
              {%
                \unexpanded{\unexpanded\expandafter}%
                  {\unexpanded{#1}\cmdconstructor@arglist1}%
              }%
              {}%
          }%
        }%
      \expandafter
    \endgroup
    \expandafter\cmdconstructor@a\cmdconstructor@tmp{#1}{#3}%
  }
\newcommand*\cmdconstructor@argspec[1]
  {%
    \def\cmdconstructor@tmp##1#1##2;{##1#1}%
    \edef\cmdconstructor@tmp
      {%
        \unexpanded\expandafter\expandafter\expandafter
          {%
            \expandafter\@gobble
            \cmdconstructor@tmp0##1##2##3##4##5##6##7##8##9;%
          }%
      }%
  }
\newcommand*\cmdconstructor@arglist[1]
  {%
    \ifnum#1>\cmdconstructor@args
    \else
      {#####1}%
      \expandafter\cmdconstructor@arglist\the\numexpr#1+1\expandafter\relax
    \fi
  }
\newcommand*\cmdconstructor@a[6]
  {\expanded{\unexpanded{#1\def#5#2}{#3\cmdconstructor@build{#6}#4}}}
\ekvsetdef\cmdconstructor@build{cmdconstructor/build}
\ekvdefinekeys{cmdconstructor/build}
  {%
     long code ex  = #1
    ,long code un  = \unexpanded{#1}
    ,noval bg      = {\iffalse}\fi
    ,noval eg      = \iffalse{\fi}
    ,noval a       = &
    ,noval p       = ##
    ,noval p1      = ##1
    ,noval p2      = ##2
    ,noval p3      = ##3
    ,noval p4      = ##4
    ,noval p5      = ##5
    ,noval p6      = ##6
    ,noval p7      = ##7
    ,noval p8      = ##8
    ,noval p9      = ##9
    ,long code str = \detokenize{#1}
    ,code  c       = \unexpanded\expandafter{\csname #1\endcsname}
    ,noval c       = \unexpanded\expandafter{\csname\iffalse}\fi
    ,noval ec      = \expandafter\endcsname\iffalse{\fi}
  }
\makeatother

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

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

\begin{document}

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

\begingroup % only to demonstrate the effect of `glob`
% two arguments, long, protected, and globally defined
\cmdconstructor\mymacro[args=2]
  {
    % will be expanded
     ex = {[\getrefnumber{somelabel}]: }
    % won't be expanded
    ,un = \niceand#1
    ,ex = \fancyiv{#2}
    % builds a csname from the argument
    ,c = fancy\roman{mycounter}
    % {
    ,bg
    % #1
    ,p1
    % }
    ,eg
    ,un = \texttt
    ,bg
    % will input the detokenized argument
    ,str = \detokenize
    ,eg
  }
% in cont the args number must be correct, all prefixes are determined by the
% last call (the exception being `glob` which might escape a group whereas a
% later `cont` doesn't if it misses the `glob` key).
\cmdconstructor\mymacro[args=2,long,prot,glob,cont]
  {
    % start a csname block
     c
    ,un = \niceand
    % end a csname block
    ,ec
  }
\endgroup

\texttt{\frenchspacing\meaning\mymacro}\par
\mymacro{xyz}{works}
\end{document}

enter image description here


Just for fun the following code is compatible to above's, but only needs a single assignment per \cmdconstructor call, while still providing all options (via expkv-cs).

\documentclass{article}

\usepackage{refcount}
\usepackage{expkv-def,expkv-cs}

\makeatletter
\newcommand*\cmdconstructor[1]
  {\unexpanded\ekvoptarg{\cmdconstructor@a#1}{}}
\newcommand*\cmdconstructor@a[2]{\cmdconstructor@options{#2}#1}
\ekvcSplit\cmdconstructor@options% >>=
  {
     { glob } = {}
    ,{ prot } = {}
    ,{ long } = {}
    ,{ args } = 0
    ,{ cont } = \@gobble
    ,{ pre }  = \@gobble
  }{\cmdconstructor@b{#1#2#3}{#4}{#5}{#6}}% =<<
\ekvdefinekeys{\string\cmdconstructor@options}% >>=
  {
     choice glob =
      {% >>=
         true    = \ekvcPass\cmdconstructor@options{ glob }\global
        ,false   = \ekvcPass\cmdconstructor@options{ glob }{}
      }% =<<
    ,noval  glob = \ekvcPass\cmdconstructor@options{ glob }\global
    ,choice prot =
      {% >>=
         true    = \ekvcPass\cmdconstructor@options{ prot }\protected
        ,false   = \ekvcPass\cmdconstructor@options{ prot }{}
      }% =<<
    ,noval  prot = \ekvcPass\cmdconstructor@options{ prot }\protected
    ,choice long =
      {% >>=
         true    = \ekvcPass\cmdconstructor@options{ long }\long
        ,false   = \ekvcPass\cmdconstructor@options{ long }{}
      }% =<<
    ,noval  long = \ekvcPass\cmdconstructor@options{ long }\long
    ,choice cont =
      {% >>=
         true    = \ekvcPass\cmdconstructor@options{ cont }\@firstofone
        ,false   = \ekvcPass\cmdconstructor@options{ cont }\@gobble
      }% =<<
    ,noval  cont = \ekvcPass\cmdconstructor@options{ cont }\@firstofone
    ,choice  pre =
      {% >>=
         true    = \ekvcPass\cmdconstructor@options{ pre }\@firstofone
        ,false   = \ekvcPass\cmdconstructor@options{ pre }\@gobble
      }% =<<
    ,noval  pre  = \ekvcPass\cmdconstructor@options{ pre }\@firstofone
    ,long code args =
      \expandafter\cmdconstructor@process@args\the\numexpr#1\relax;%
  }% =<<
\newcommand\cmdconstructor@b[6]% >>=
  {%
    \expanded
      {{%
        \unexpanded{#1\def#5}\cmdconstructor@arglist1#2\@firstofone
          {%
            #3{\cmdconstructor@call#5{#2}}%
            \cmdconstructor@build{#6}%
            #4{\cmdconstructor@call#5{#2}}%
          }%
      }}%
  }% =<<
\newcommand\cmdconstructor@arglist[3]%>>=
  {%
    \ifnum#1>#2
    \else
      #3{###1}%
      \expanded
        {%
          \unexpanded
            {\expandafter\cmdconstructor@arglist\the\numexpr#1+1\relax#2{#3}}%
          \expandafter
        }%
    \fi
  }% =<<
\newcommand\cmdconstructor@call[2]% >>=
  {%
    \unexpanded\expandafter\expandafter\expandafter
      {\expandafter#1\expanded{\cmdconstructor@arglist1#2{}}}%
  }% =<<
\def\cmdconstructor@process@args#1;%>>=
  {%
    \ifnum#1<\z@
      \ekverr{cmdconstructor}{Too few arguments.}%
      \ekvcPass\cmdconstructor@options{ args }{0}%
    \else
      \ifnum#1>9
        \ekverr{cmdconstructor}{Too many arguments.}%
        \ekvcPass\cmdconstructor@options{ args }{9}%
      \else
        \ekvcPass\cmdconstructor@options{ args }{#1}%
      \fi
    \fi
  }% =<<
\newcommand\cmdconstructorOptions{\ekvcChange\cmdconstructor@options}
\ekvsetdef\cmdconstructor@build{cmdconstructor/build}
\ekvdefinekeys{cmdconstructor/build}% >>=
  {%
     long code ex  = #1
    ,long code un  = \unexpanded{#1}
    ,noval bg      = {\iffalse}\fi
    ,noval eg      = \iffalse{\fi}
    ,noval a       = &
    ,noval p       = ##
    ,noval p1      = ##1
    ,noval p2      = ##2
    ,noval p3      = ##3
    ,noval p4      = ##4
    ,noval p5      = ##5
    ,noval p6      = ##6
    ,noval p7      = ##7
    ,noval p8      = ##8
    ,noval p9      = ##9
    ,long code str = \detokenize{#1}
    ,code  c       = \unexpanded\expandafter{\csname #1\endcsname}
    ,noval c       = \unexpanded\expandafter{\csname\iffalse}\fi
    ,noval ec      = \expandafter\endcsname\iffalse{\fi}
  }% =<<
\makeatother

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

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

\begin{document}

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

\begingroup % only to demonstrate the effect of `glob`
% two arguments, long, protected, and globally defined
\cmdconstructor\mymacro[args=2]
  {
    % will be expanded
     ex = {[\getrefnumber{somelabel}]: }
    % won't be expanded
    ,un = \niceand#1
    ,ex = \fancyiv{#2}
    % builds a csname from the argument
    ,c = fancy\roman{mycounter}
    % {
    ,bg
    % #1
    ,p1
    % }
    ,eg
    ,un = \texttt
    ,bg
    % will input the detokenized argument
    ,str = \detokenize
    ,eg
  }
% in cont the args number must be correct, all prefixes are determined by the
% last call (the exception being `glob` which might escape a group whereas a
% later `cont` doesn't if it misses the `glob` key).
\cmdconstructor\mymacro[args=2,long,prot,glob,cont]
  {
    % start a csname block
     c
    ,un = \niceand
    % end a csname block
    ,ec
  }
\endgroup

\texttt{\frenchspacing\meaning\mymacro}\par
\mymacro{xyz}{works}
\end{document}
2

You wish to accumulate tokens that form the definition-text of a macro by and by?

I sometimes do such things, too. Usually I do so by means of macros which recursively call themselves and in one of their arguments per iteration by and by accumulate the desired set of tokens.
This way you don't need so many intermediate assignments.

In case you intend to use \AddUnexpandedToMacro/\AddExpandedToMacro etc as helper-macros with similar recursive routines I recommend reconsidering whether a (tail-)recursive expansion-based approach where desired tokens are accumulated within a macro-argument might be feasible as well.

Be that as it may.

TeX takes the .tex-input-file for a set of instructions for appending tokens to the token-stream.
Tokens may be explicit character tokens or control sequence tokens.
The expansion of macros and the processing of their arguments takes place when tokenization is already done.
You say you want \AddCharToMacro{c} to append the special character "command (\)" to the macro under construction.

What is this special character "command (\)" about?

If things are processed in terms of macro-arguments, then they are processed as tokens, thus—as things already got tokenized—there is no need to take the backslash into special consideration.
If things are to be tokenized under catcode-12-régime so that re-tokenizing them via \scantokens when the usual catcode-régime is in effect yields the actually desired set of tokens, then there is no need to take the backslash into special consideration either. But expanding things is not possible under catcode-12-régime because explicit character-tokens of category 12 are not expandable.

I assume you wish stuff to be done in terms of macro-arguments and tokens.
In this case only unbalanced braces require special consideration.

% Compile with a LaTeX-engine where the primitives `\expanded` and
% `\unexpanded` are available.
%
\makeatletter
\newcommand\StartMacro[1]{\newcommand*#1{}}%
\newcommand\BuildMacro[3]{%
  \@ifundefined{\expandafter\@gobble\string#2}{\@latex@error{Buffer \string#2\space is undefined!}\@eha}{%
     \expanded{\unexpanded{\let#2=\relax#1#2#3}{#2}}%
  }%
}%
\newcommand\AppendUnexpandedToMacro[2]{\edef#1{\unexpanded\expandafter{#1\unexpanded{#2}}}}%
\newcommand\AppendCsTokenFromCsNameToMacro[2]{%
  \edef#1{\unexpanded\expandafter{#1}\unexpanded\expandafter{\expandafter\unexpanded\expandafter{\csname#2\endcsname}}}%
}%
\newcommand\AppendExpandedToMacro[2]{%
  \@ifundefined{\expandafter\@gobble\string#1}{\@latex@error{Buffer \string#1\space is undefined!}\@eha}{%
     \let\@@protect\protect
     \let\protect\@unexpandable@protect
     \afterassignment\restore@protect
     \edef#1{\unexpanded\expandafter{#1}\unexpanded\expandafter{\expandafter\unexpanded\expandafter{\expanded{#2}}}}%
  }%
}%
\newcommand\AppendLeftBraceToMacro[1]{%
  \@ifundefined{\expandafter\@gobble\string#1}{\@latex@error{Buffer \string#1\space is undefined!}\@eha}{%
     \edef#1{\unexpanded\expandafter{#1}\unexpanded{{\expandafter\@gobble\string}}}%
  }%
}%
\newcommand\AppendRightBraceToMacro[1]{%
  \@ifundefined{\expandafter\@gobble\string#1}{\@latex@error{Buffer \string#1\space is undefined!}\@eha}{%
     \edef#1{\unexpanded\expandafter{#1}\unexpanded{\expandafter\@gobble\string{}}}%
  }%
}%
\newcommand\PrependUnexpandedToMacro[2]{\edef#1{\unexpanded{\unexpanded{#2}}\unexpanded\expandafter{#1}}}%
\newcommand\PrependCsTokenFromCsNameToMacro[2]{%
  \edef#1{\unexpanded\expandafter{\expandafter\unexpanded\expandafter{\csname#2\endcsname}}\unexpanded\expandafter{#1}}%
}%
\newcommand\PrependExpandedToMacro[2]{%
  \@ifundefined{\expandafter\@gobble\string#1}{\@latex@error{Buffer \string#1\space is undefined!}\@eha}{%
     \let\@@protect\protect
     \let\protect\@unexpandable@protect
     \afterassignment\restore@protect
     \edef#1{\unexpanded\expandafter{\expandafter\unexpanded\expandafter{\expanded{#2}}}\unexpanded\expandafter{#1}}%
  }%
}%
\newcommand\PrependLeftBraceToMacro[1]{%
  \@ifundefined{\expandafter\@gobble\string#1}{\@latex@error{Buffer \string#1\space is undefined!}\@eha}{%
     \edef#1{\unexpanded{{\expandafter\@gobble\string}}\unexpanded\expandafter{#1}}%
  }%
}%
\newcommand\PrependRightBraceToMacro[1]{%
  \@ifundefined{\expandafter\@gobble\string#1}{\@latex@error{Buffer \string#1\space is undefined!}\@eha}{%
     \edef#1{\unexpanded{\expandafter\@gobble\string{}}\unexpanded\expandafter{#1}}%
  }%
}%
\makeatother

\StartMacro{\foobar}%
\AppendUnexpandedToMacro{\foobar}{This }%
\AppendUnexpandedToMacro{\foobar}{is the 1st argument: }%
\AppendUnexpandedToMacro{\foobar}{#1}%
\AppendUnexpandedToMacro{\foobar}{\\}%
\AppendUnexpandedToMacro{\foobar}{This }%
\AppendUnexpandedToMacro{\foobar}{is the 1st argument again: }%
\AppendUnexpandedToMacro{\foobar}{#}%
\AppendUnexpandedToMacro{\foobar}{1}%
\AppendUnexpandedToMacro{\foobar}{\\}%
\AppendLeftBraceToMacro{\foobar}%
\AppendCsTokenFromCsNameToMacro{\foobar}{LaTeX}%
\AppendUnexpandedToMacro{\foobar}{ is fun!}%
\AppendRightBraceToMacro{\foobar}%
\AppendUnexpandedToMacro{\foobar}{\\}%
\def\tempa{\empty\tempb}
\def\tempb{This }%
\AppendExpandedToMacro{\foobar}{\tempa}%
\def\tempb{is the 2nd argument: }%
\AppendExpandedToMacro{\foobar}{\tempa}%
\def\tempb{##2}%
\AppendExpandedToMacro{\foobar}{\tempa}%
\AppendLeftBraceToMacro{\foobar}%
\AppendLeftBraceToMacro{\foobar}%
\def\tempb{This is in braces.}%
\AppendExpandedToMacro{\foobar}{\tempa}%
\AppendRightBraceToMacro{\foobar}%
\AppendRightBraceToMacro{\foobar}%
\PrependUnexpandedToMacro{\foobar}{ }%
\PrependRightBraceToMacro{\foobar}%
\PrependUnexpandedToMacro{\foobar}{is fun!}%
\PrependExpandedToMacro{\foobar}{ \empty\empty\empty}%
\PrependCsTokenFromCsNameToMacro{\foobar}{LaTeX}%
\PrependLeftBraceToMacro{\foobar}%
% \BuildMacro{<definition-command>}{<stack/command that is to be defined>}{<parameter-text with syntax according to <definition-command>>}%
\BuildMacro{\newcommand*}{\foobar}{[2]}%
% You could also try something like
%   \BuildMacro{\def}{\foobar}{#1#2}%
% or
%   \BuildMacro{\NewDocumentCommand}{\foobar}{{mm}}%
%   \expandafter\show\csname foobar code\endcsname
\show\foobar

\stop

Output on terminal and in .log-file reveals the definition of \foobar:

> \foobar=macro:
#1#2->{\LaTeX  is fun!} This is the 1st argument: #1\\This is the 1st argument 
again: #1\\{\LaTeX  is fun!}\\This is the 2nd argument: #2{{This is in braces.}
}.

This is what I expected to get from the code above.

With each of the macros for maintaining the macro-definition-text-stack you need to provide a first argument which denotes which stack/which macro's definition-text things shall be applied to.
This way you can have several macro-definition-text-stacks simultaneously/in parallel.

With \AppendExpandedToMacro/\PrependExpandedToMacro expansion is not delayed until \BuildMacro is carried out but is done immediately. This way you can use/redefine scratch-macros between different calls to \AppendExpandedToMacro/\PrependExpandedToMacro.
You said "When using LaTeX", so LaTeX 2ε's \protect-mechanism is taken into account.

You must log in to answer this question.

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