2

I have many commands that require up to 8 parameters where there are many places that the various arguments are used. It would be nice to be able to name the arguments within the command to make the code more understandable. Here's an MWE using the C-Preprocessor that shows what I'm trying to achieve. How do I do this without cpp?

\documentclass{article}

\usepackage{tikz}
\usepackage{pgfplots}

\newcommand{\mydraw}[1]{%

#define MYVALUE #1

  \addplot [mark=*] coordinates{(MYVALUE,0.3)};
  \draw node at (axis cs:MYVALUE,0.4) [below] {MYVALUE};
}
 
\begin{document}

\begin{tikzpicture}
  \begin{axis}

    \addplot coordinates{(0,0) (1,1)};

    \mydraw{0.4}
    \mydraw{0.6}

    \end{axis}
\end{tikzpicture}

\end{document}

The desired output is shown below:

enter image description here

Shown below are some of the things I've tried. Currently, I use pgfkeys and pgfplotsextra as described in (Expansion of addplot and draw are different in tikz?), but this is becoming problematic because, for example, multiple calls to commands like \mydraw fail. I'm looking for a simpler solution that pgfkeys. Part of what is making this hard for me is the funky two pass thing that happens in draw. Some of these compile, but because of the two pass processing, the label is dropped for one of the points.

% This I can't get to compile to test.
%\let\MYVALUE=#1

% \def doesn't have the command/renewcommand problem.  It
% just fails the two pass thing.
%
%\def\MYVALUE{#1}

% \newcommand doesn't work because I have to know when I
% am redefining something to use \renewcommand.  If I define
%\newcommand \MYVALUE outside of \mydraw, it fails the two
% pass thing.
%
%\newcommand\MYVALUE{#1}

%  \addplot [mark=*] coordinates{(\MYVALUE,0.3)};
%  \draw node at (axis cs:\MYVALUE,0.4) [below] {\MYVALUE};

Please note that I am not trying to do this (How to name command arguments). From the point of view of the caller, this should look like a "normal" command. I'm trying to give the arguments names within the command that are not visible to the caller.

UPDATE:

Of the answers provided, the one from egreg is the best per the original definition of the problem. I've marked this as the answer.

I was not expecting answers to rewrite the declaration of the commands, so I did not include the need for optional arguments in the problem. Given this need as well, the namedef answer provided in the comments is the actual answer that I'm deploying.

7
  • 2
    I think you want something like this? Nov 15, 2020 at 3:41
  • Do you know if this works with the survey phase/do it phase of pgf? (see: tex.stackexchange.com/questions/525053/…) Nov 15, 2020 at 3:50
  • I don't know much about pgfplots, but as far as I can see it should work. namedef will act at the time you define the macro (no named parameters will remain after the definition). After the definition is done, it is indistinguishable if you did or did not use namedef. (namedef scans the definition, replaces the named parameters (#[whatever]) by numbered ones (#1), then passes the definition on to TeX do its thing). Try this: pastebin.com/raw/a9pwEvvT Nov 15, 2020 at 3:57
  • When I compiled namedef, I did not seem to get a definition for \newnamedcommand. How do I get the definition for this function? (\def doesn't allow optional arguments) Nov 15, 2020 at 4:38
  • 6
    Duplicate of Use of named arguments instead of numbers in LaTeX. (although there are some good answers here as well, might be better if the questions are merged
    – user202729
    Jul 4 at 12:03

3 Answers 3

1

So long as the placeholders are not used in different ways and only consist of ASCII characters, you can do

\documentclass{article}
%\usepackage{xparse} % not needed with LaTeX 2020-10-01 or later

\ExplSyntaxOn
\NewDocumentCommand{\varnewcommand}{mmm}
 {
  % #1 = command to define
  % #2 = list of arguments
  % #3 = replacement text
  \donham_varnewcommand:Nnn #1 { #2 } { #3 }
 }

\seq_new:N \l__donham_varnewcommand_args_seq
\tl_new:N \l__donham_varnewcommand_replacement_tl
\tl_new:N \l__donham_varnewcommand_name_tl
\cs_generate_variant:Nn \cs_set:Nn { cV }

\cs_new_protected:Nn \donham_varnewcommand:Nnn
 {
  % make a sequence from the argument name list
  \seq_set_from_clist:Nn \l__donham_varnewcommand_args_seq { #2 }
  % build an internal function name from the command we want to define
  % it needs to end in : followed by as many n as the number of args
  \tl_set:Nx \l__donham_varnewcommand_name_tl
   {
    \cs_to_str:N #1 : \prg_replicate:nn { \seq_count:N \l__donham_varnewcommand_args_seq } { n }
   }
  % save the replacement text in a tl variable
  \tl_set:Nn \l__donham_varnewcommand_replacement_tl { #3 }
  % now cycle through the sequence of arguments
  \seq_map_indexed_function:NN \l__donham_varnewcommand_args_seq \__donham_varnewcommand_replace:nn
  % the replacements have been done; define the internal function
  \cs_set:cV  { \l__donham_varnewcommand_name_tl } \l__donham_varnewcommand_replacement_tl
  % now set #1 to be the same as the just built function
  \cs_new_eq:Nc #1 { \l__donham_varnewcommand_name_tl }
 }

\cs_new_protected:Nn \__donham_varnewcommand_replace:nn
 {
  % replace the k-th item in the sequence with #<k>
  \regex_replace_all:nnN { #2 } { \cP\# #1 } \l__donham_varnewcommand_replacement_tl
 }

\ExplSyntaxOn

\varnewcommand{\mydraw}{MYVALUE}{%
  \addplot [mark=*] coordinates{(MYVALUE,0.3)};
  \draw node at (axis cs:MYVALUE,0.4) [below] {MYVALUE};
}

\varnewcommand{\foo}{AAA,BBB,CCC}{%
  AAA-BBB-CCC-AAA%
}

The second argument to \varnewcommand should be a list of the strings, in any order. The first item will be replaced by #1, the second by #2 and so on.

This will translate into

> \mydraw=\long macro:
#1->\addplot [mark=*]coordinates{(#1,0.3)};\draw nodeat(axiscs:#1,0.4)[below]{#1};.
l.47 \show\mydraw

?
> \foo=\long macro:
#1#2#3->#1-#2-#3-#1.
l.49 \show\foo
5
  • Is there a way to use default arguments with this solution/ Nov 15, 2020 at 12:56
  • @ChristopherDonham In what sense? Do you want to mix MYVALUE and #1?
    – egreg
    Nov 15, 2020 at 13:06
  • I'm getting compile errors on the code: This is pdfTeX, Version 3.14159265-2.6-1.40.20 ... ! Undefined control sequence. \donham_varnewcommand:Nnn ..._indexed_function:NN \l__donham_varnewcommand_a... l.49 } Nov 15, 2020 at 16:32
  • With regards to my question about default arguments, could you please provide an example of how I would do the following using your solution? \newcommand{\mydraw}[2][color=blue]{% \addplot [mark=*,#1] coordinates{(#2,0.3)}; \draw node at (axis cs:#2,0.4) [below] {#2}; } Nov 15, 2020 at 16:36
  • @ChristopherDonham You need to have an up-to-date version of the TeX system, I'm afraid. Optional arguments? Did you ask for them?
    – egreg
    Nov 15, 2020 at 16:47
2

You use pgfplots which is based on pgf, so you can use pgf keys.

\documentclass{article}
\usepackage{pgfplots}
\pgfplotsset{compat=1.17}
\pgfkeys{/my args/.cd,my value/.initial=0}%

\newcommand{\mydraw}[1]{%
\pgfkeys{/my args/.cd,#1}%
\edef\temp{% <- this is necessary because of the way pgfplots surveys and executes stuff
  \noexpand\addplot [mark=*] coordinates{(\pgfkeysvalueof{/my args/my value},0.3)};
  \noexpand\draw node at (axis cs:\pgfkeysvalueof{/my args/my value},0.4) [below] {\pgfkeysvalueof{/my args/my value}};
  }%
\temp  
}
 
\begin{document}

\begin{tikzpicture}
  \begin{axis}

    \addplot coordinates{(0,0) (1,1)};

    \mydraw{my value=0.4}
    \mydraw{my value=0.6}

    \end{axis}
\end{tikzpicture}

\end{document}

enter image description here

Of course, you can define a version in which you do not need my value= but then it is easier to get confused.

\documentclass{article}
\usepackage{pgfplots}
\pgfplotsset{compat=1.17}
\pgfkeys{/my args/.cd,my value/.initial=0}%

\newcommand{\mydraw}[1]{%
\pgfkeys{/my args/.cd,my value=#1}%
\edef\temp{% <- this is necessary because of the way pgfplots surveys and executes stuff
  \noexpand\addplot [mark=*] coordinates{(\pgfkeysvalueof{/my args/my value},0.3)};
  \noexpand\draw node at (axis cs:\pgfkeysvalueof{/my args/my value},0.4) [below] {\pgfkeysvalueof{/my args/my value}};
  }%
\temp  
}
 
\begin{document}

\begin{tikzpicture}
  \begin{axis}

    \addplot coordinates{(0,0) (1,1)};

    \mydraw{0.4}
    \mydraw{0.6}

    \end{axis}
\end{tikzpicture}

\end{document}
6
  • I currently use pgfkeys, but this makes the code fairly complicated with as shown by your example above. I was hoping for something simple that did not require me to wrap all of my code in \edef\temp. The idea was to simplify the code to make it easier to understand. Nov 15, 2020 at 3:36
  • @ChristopherDonham The \edef\temp stuff is specifically needed in pgfplots but generally not necessary.
    – user228539
    Nov 15, 2020 at 3:41
  • All the code I am working on uses pgfplots. Is there a way to do this without pgfkeys? It really obfuscates the code when the goal was simplification. Nov 15, 2020 at 3:43
  • I end up having to use pgfplotsextra in order to make pgfkeys work. It just seems like a mess for someone like me who is not skilled in LaTeX. Nov 15, 2020 at 3:45
  • @ChristopherDonham Well, I do not know what you are doing, nor why you need \pgfextra.
    – user228539
    Nov 15, 2020 at 3:47
1

Off the cuff I can only offer a macro \ReplacementScope which takes two arguments:

The first argument is a comma-separated list of ⟨tokens to replace⟩=⟨replacement⟩-pairs.

The second argument is a ⟨brace balanced list of tokens⟩.

Within ⟨brace balanced list of tokens⟩ each instance of ⟨tokens to replace⟩ will be replaced by ⟨replacement⟩ for each ⟨tokens to replace⟩=⟨replacement⟩-pair.

!!! Use with caution !!!

  1. \ReplacementScope as a side-effect also replaces all pairs of matching explicit character tokens of catcode 1(begin group)/2(end group) by matching curly braces {1(begin group) / }2(end group)!!!
  2. ⟨tokens to replace⟩ must not contain explicit character tokens of catcode 1(begin group) or 2(end group)!!!
  3. ⟨tokens to replace⟩ must not contain tokens of catcode 6(parameter)!!!
  4. Defines temporary macros, therefore not fully expandable!!!
  5. You need to take care of correct order in which replacements shall take place!!!

You can use \ReplacementScope for replacing text-strings, e.g., by #1 or by #2 or by ##1 or whatever.

Ad item 1:

Under normal catcode-régime the only character of catcode 1 is the curly left brace and the only character of catcode 2 is the curly right brace. So under normal catcode-régime the replacement of character tokens of catcode 1/2 by matching curly braces does not yield a difference. Even if things got tokenized under unusual catcode-régime, the replacement usually doesn't matter. But there are edge cases.

E.g., if you have assigned catcode 1 to the character X, you can use X like { and, e.g., do:
\def\tempaXdefinition of tempa}%.

This will be tokenized as:

\def, \tempa, X1, d11, e11, f11, i11, n11, i11, t11, i11, o11, n11, ⟨space⟩10, o11, f11, ⟨space⟩10, t11, e11, m11, p11, a11, }2.

If this is done within \ReplacementScope, then this will be turned to

\def, \tempa, {1, d11, e11, f11, i11, n11, i11, t11, i11, o11, n11, ⟨space⟩10, o11, f11, ⟨space⟩10, t11, e11, m11, p11, a11, }2.

At first glimpse this doesn't matter.

Assume you wish for some obscure reason to apply #{-notation for defining a macro \tempa whose argument is delimited by an explicit X-character-token of category-code 1(begin group) which will be left in place.
You would do:

\catcode`\X=1
\def\tempa#1#Xdefinition of tempa}%

The second line would be tokenized as:

\def, \tempa, #6, 112, #6, X1, d11, e11, f11, i11, n11, i11, t11, i11, o11, n11, ⟨space⟩10, o11, f11, ⟨space⟩10, t11, e11, m11, p11, a11, }2.

Inside \ReplacementScope this will be turned into:

\def, \tempa, #6, 112, #6, {1, d11, e11, f11, i11, n11, i11, t11, i11, o11, n11, ⟨space⟩10, o11, f11, ⟨space⟩10, t11, e11, m11, p11, a11, }2.

This makes a difference:

Instead of being delimited by X1(begin group) the argument of \tempa will be delimited by {1(begin group) and this affects the delimiter-matching.

Ad item 5:

The order in which replacements occur makes a difference:

E.g.,

\ReplacementScope{VARIABLEA=#1, INNERVARIABLEA=##1}{%
  \newcommand\Outside[1]{%
    \newcommand\Inside[1]{%
      VARIABLEA and INNERVARIABLEA
    }%
  }%
}%

yields

\newcommand\Outside[1]{%
  \newcommand\Inside[1]{%
    #1 and INNER#1
  }%
}%

whereas

\ReplacementScope{INNERVARIABLEA=##1, VARIABLEA=#1}{%
  \newcommand\Outside[1]{%
    \newcommand\Inside[1]{%
      VARIABLEA and INNERVARIABLEA
    }%
  }%
}%

yields

\newcommand\Outside[1]{%
  \newcommand\Inside[1]{%
    #1 and ##1
  }%
}%

I think as long as you don't do intriguing plain-TeX-\def\macro#1#⟨non-brace-character of catcode 1⟩-trickery you will be pretty safe.

Nevertheless I am not really a fan of such things:

First, you get one more level of processing which you have to take into account when tracking bugs.

Second, I see here an attempt to transfer things to TeX that are related to programming paradigms other than those given in TeX. In my experience, such undertakings are very susceptible to difficulties that can only be solved if you are very familiar with the programming paradigms of TeX. But if you are, then you can do without such transfers from other areas of programming.

Third, probably the replacement-task can be performed by the editor/by the software in use for writing the .tex-file.

Fourth, probably LuaTeX-based TeX-engines are more suitable for this kind of undertaking.


\documentclass[a4paper]{article}

\makeatletter
%%//////////////////////// Code of \ReplacementScope: /////////////////////////
%%=============================================================================
\RequirePackage{pgfkeys}%
\pgfkeys{%
  /ReplacementScope/.unknown/.code=%
  \expandafter\UD@AddToReplacementlist\expandafter{\pgfkeyscurrentname}{#1},%
}%
\newcommand*\UD@Replacementlist{}%
\newcommand\UD@AddToReplacementlist[2]{%
  \expandafter\UD@Exchange
  \expandafter{\expandafter\toks@\expandafter{\the\toks@}}{%
    \toks@{\UD@Revert{#1}{#2}}%
    \ifx\UD@Replacementlist\empty
      \toks@\expandafter{\the\toks@{\endgroup\@firstofone}}%
    \else
      \toks@\expandafter{%
        \the\expandafter\toks@\expandafter{\UD@Replacementlist}%
      }%
    \fi
    \edef\UD@Replacementlist{\the\toks@}%
  }%
}%
\newcommand\UD@Revert[4]{%
  #3{\UD@Replace{#1}{#2}{#4}}%
}%
\newcommand\ReplacementScope[1]{%
  \begingroup
  \pgfkeys{/ReplacementScope/.cd,#1,}%
  \ifx\UD@Replacementlist\empty\endgroup\expandafter\@secondoftwo\fi
  \UD@Replacementlist{\@firstofone}%
}%
%%=============================================================================
%%//////////////////// End of code of \ReplacementScope: //////////////////////

%%
%%//////////////////// Code of my own replacement-routine: ////////////////////
%%=============================================================================
%% Paraphernalia:
%%    \UD@firstoftwo, \UD@secondoftwo,
%%    \UD@PassFirstToSecond, \UD@Exchange, \UD@removespace
%%    \UD@CheckWhetherNull, \UD@CheckWhetherBrace,
%%    \UD@CheckWhetherLeadingTokens, \UD@ExtractFirstArg
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@Exchange[2]{#2#1}%
\newcommand\UD@removespace{}\UD@firstoftwo{\def\UD@removespace}{} {}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is empty>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral\expandafter\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\z@\UD@secondoftwo}%
  {\expandafter\z@\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's first token is a catcode-1-character
%%.............................................................................
%% \UD@CheckWhetherBrace{<Argument which is to be checked>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked has leading
%%                        catcode-1-token>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked has no leading
%%                        catcode-1-token>}%
\newcommand\UD@CheckWhetherBrace[1]{%
  \romannumeral\expandafter\UD@secondoftwo\expandafter{\expandafter{%
  \string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\z@\UD@firstoftwo}%
  {\expandafter\z@\UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's leading tokens form a specific 
%% token-sequence that does neither contain explicit character tokens of 
%% category code 1 or 2 nor contain tokens of category code 6:
%%.............................................................................
%% \UD@CheckWhetherLeadingTokens{<argument which is to be checked>}%
%%                              {<a <token sequence> without explicit 
%%                                character tokens of category code
%%                                1 or 2 and without tokens of
%%                                category code 6>}%
%%                              {<internal token-check-macro>}%
%%                              {<tokens to be delivered in case
%%                                <argument which is to be checked> has
%%                                <token sequence> as leading tokens>}%
%%                              {<tokens to be delivered in case 
%%                                <argument which is to be checked>
%%                                does not have <token sequence> as
%%                                leading tokens>}%
\newcommand\UD@CheckWhetherLeadingTokens[3]{%
  \romannumeral\UD@CheckWhetherNull{#1}{\expandafter\z@\UD@secondoftwo}{%
    \expandafter\UD@secondoftwo\string{\expandafter
    \UD@@CheckWhetherLeadingTokens#3{\relax}#1#2}{}}%
}%
\newcommand\UD@@CheckWhetherLeadingTokens[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
  {\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
  {\expandafter\expandafter\expandafter\expandafter
   \expandafter\expandafter\expandafter\z@\expandafter\expandafter
   \expandafter}\expandafter\UD@secondoftwo\expandafter{\string}%
}%
%%-----------------------------------------------------------------------------
%% \UD@internaltokencheckdefiner{<internal token-check-macro>}%
%%                              {<token sequence>}%
%% Defines <internal token-check-macro> to snap everything 
%% until reaching <token sequence>-sequence and spit that out
%% nested in braces.
%%-----------------------------------------------------------------------------
\newcommand\UD@internaltokencheckdefiner[2]{%
  \@ifdefinable#1{\long\def#1##1#2{{##1}}}%
}%
\UD@internaltokencheckdefiner{\UD@InternalExplicitSpaceCheckMacro}{ }%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%
%%   \romannumeral\UD@ExtractFirstArgLoop{ABCDE\UD@SelDOm} yields  {A}
%%
%%   \romannumeral\UD@ExtractFirstArgLoop{{AB}CDE\UD@SelDOm} yields  {AB}
%%.............................................................................
\@ifdefinable\UD@RemoveTillUD@SelDOm{%
  \long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
  {\z@#1}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%=============================================================================
%% \UD@Replace{<token-sequence to replace>}%
%%            {<replacement>}%
%%            {<tokens afterwards>}
%%            {<token list>}
%%
%% Replaces all instances of <token-sequence to replace> in <token list>
%% by <replacement>. The result will be nested in curly braces and
%% preceeded by <tokens afterwards>.
%%
%% !!! Does also replace all pairs of matching explicit character tokens of
%%     catcode 1/2 by matching braces!!!
%% !!! <token-sequence to replace> must not contain explicit character tokens
%%     of catcode 1 or 2 !!!
%% !!! <token-sequence to replace> must not contain tokens of catcode 6 !!!
%% !!! Defines temporary macro \UD@temp, therefore not expandable !!!
%%-----------------------------------------------------------------------------
\newcommand\UD@Replace[4]{%
  % #1 - <token-sequence to replace>
  % #2 - <replacement>
  % #3 - <tokens afterwards>
  % #4 - <token list>
  \begingroup
  \UD@internaltokencheckdefiner{\UD@temp}{#1}%
  \expandafter\endgroup
  \romannumeral\UD@ReplaceLoop{#4}{}{#1}{#2}{#3}%
}%
\newcommand\UD@ReplaceLoop[5]{%
  % Do:
  %  \UD@internaltokencheckdefiner{\UD@temp}{<token-sequence to replace>}%
  %  \romannumeral\UD@ReplaceLoop{<token list>}%
  %                              {<sequence created so far, initially empty>}%
  %                              {<token-sequence to replace>}%
  %                              {<replacement>}%
  %                              {<tokens afterwards>}%
  %
  % #1 - <token list>
  % #2 - <sequence created so far, initially empty>
  % #3 - <token-sequence to replace>
  % #4 - <replacement>
  % #5 - <tokens afterwards>
  \UD@CheckWhetherNull{#1}{\z@#5{#2}}{%
    \UD@CheckWhetherLeadingTokens{#1}{#3}{\UD@temp}{%
      \expandafter\expandafter\expandafter\UD@ReplaceLoop
      \expandafter\expandafter\expandafter{%
        \expandafter\UD@firstoftwo\expandafter{\expandafter}\UD@temp#1%
      }{#2#4}%
    }{%
      \UD@CheckWhetherLeadingTokens{#1}{ }{\UD@InternalExplicitSpaceCheckMacro}{%
         \expandafter\UD@ReplaceLoop
         \expandafter{\UD@removespace#1}{#2 }%
      }{%
        \UD@CheckWhetherBrace{#1}{%
          \expandafter\expandafter\expandafter\UD@PassFirstToSecond
          \expandafter\expandafter\expandafter{%
          \expandafter\UD@PassFirstToSecond\expandafter{%
            \romannumeral\expandafter\expandafter\expandafter\z@
            \romannumeral\expandafter\UD@ReplaceLoop
              \romannumeral
              \UD@ExtractFirstArgLoop{#1\UD@SelDOm}{}{#3}{#4}{\@firstofone}%
          }{#2}}%
          {\expandafter\UD@ReplaceLoop\expandafter{\UD@firstoftwo{}#1}}%
        }{%
          \expandafter\UD@PassFirstToSecond\expandafter{%
            \romannumeral
            \expandafter\UD@Exchange
            \romannumeral\UD@ExtractFirstArgLoop{#1\UD@SelDOm}{\z@#2}%
          }{\expandafter\UD@ReplaceLoop\expandafter{\UD@firstoftwo{}#1}}%
        }%
      }%
    }%
    {#3}{#4}{#5}%
  }%
}%
\makeatother
%%=============================================================================
%%///////////////// End of code of my own replacement-routine. ////////////////
\makeatother


\ReplacementScope{VAR_A=#1, VAR_B=#2, VAR_C=#3}{%
  %
  \newcommand{\myfirstcommand}[3]{%
    Argument 1 is VAR_A.\\
    Argument 2 is VAR_B.\\
    Argument 3 is VAR_C.
  }%
  %
}


\ReplacementScope{}{%
  %
  \newcommand{\mysecondcommand}[3]{%
    Three arguments are gobbled.
    This sentence contains the string VAR_A.\\
    This sentence contains the string VAR_B.\\
    This sentence contains the string VAR_C.\\
  }%
  %
}

\ReplacementScope{INNERVAR_A=##1, INNERVAR_B=##2, INNERVAR_C=##3, VAR_A=#1, VAR_B=#2, VAR_C=#3}{%
  %
  \newcommand{\mythirdcommand}[3]{%
    \newcommand{\myinnerthirdcommand}[3]{%
      Argument 1 is INNERVAR_A.\\
      Argument 2 is INNERVAR_B.\\
      Argument 3 is INNERVAR_C.
    }%
    \myinnerthirdcommand{VAR_A}{VAR_B}{VAR_C}%
  }%
  %
}


\usepackage{tikz}
\usepackage{pgfplots}

\ReplacementScope{MYVALUE=#1}{%
  %
  \newcommand{\mydraw}[1]{%
    \addplot [mark=*] coordinates{(MYVALUE,0.3)};
    \draw node at (axis cs:MYVALUE,0.4) [below] {MYVALUE};
  }%
  %
}%


\begin{document}

\begingroup

\ttfamily\footnotesize

\begin{verbatim}
\ReplacementScope{VAR_A=#1, VAR_B=#2, VAR_C=#3}{%
  %
  \newcommand{\myfirstcommand}[3]{%
    Argument 1 is VAR_A.\\
    Argument 2 is VAR_B.\\
    Argument 3 is VAR_C.
  }%
  %
}
\end{verbatim}
\string\myfirstcommand=\meaning\myfirstcommand

\vfill\hrule\vfill

\begin{verbatim}
\ReplacementScope{}{%
  %
  \newcommand{\mysecondcommand}[3]{%
    Three arguments are gobbled.
    This sentence contains the string VAR_A.\\
    This sentence contains the string VAR_B.\\
    This sentence contains the string VAR_C.\\
  }%
  %
}
\end{verbatim}
\string\mysecondcommand=\meaning\mysecondcommand

\vfill\hrule\vfill

\begin{verbatim}
\ReplacementScope{INNERVAR_A=##1, INNERVAR_B=##2, INNERVAR_C=##3, VAR_A=#1, VAR_B=#2, VAR_C=#3}{%
  %
  \newcommand{\mythirdcommand}[3]{%
    \newcommand{\myinnerthirdcommand}[3]{%
      Argument 1 is INNERVAR_A.\\
      Argument 2 is INNERVAR_B.\\
      Argument 3 is INNERVAR_C.
    }%
    \myinnerthirdcommand{VAR_A}{VAR_B}{VAR_C}%
  }%
  %
}
\end{verbatim}
\string\mythirdcommand=\meaning\mythirdcommand

\vfill\hrule\vfill

\begin{verbatim}
\ReplacementScope{MYVALUE=#1}{%
  %
  \newcommand{\mydraw}[1]{%
    \addplot [mark=*] coordinates{(MYVALUE,0.3)};
    \draw node at (axis cs:MYVALUE,0.4) [below] {MYVALUE};
  }%
  %
}%
\end{verbatim}
\string\mydraw=\meaning\mydraw

\vfill\hrule\vfill\vfill

\endgroup

\newpage

\begin{tikzpicture}
  \begin{axis}

    \addplot coordinates{(0,0) (1,1)};

    \mydraw{0.4}
    \mydraw{0.6}

    \end{axis}
\end{tikzpicture}

\end{document}

enter image description here

enter image description here



Edit in November 17, 2020:

It just came to my mind that with another approach you get rid of the restrictions mentioned in items 1 - 4:

Have \ReplacementScope read its arguments under verbatim-catcode-régime, e.g., v/+v-type of xparse, put the ⟨brace balanced list of tokens⟩ into an expl3-token-list-variable and iterate the comma-separated list of ⟨tokens to replace⟩=⟨replacement⟩-pairs via \keyval_parse:NNn for recursively calling expl3's \tl_replace_all:Nnn ⟨tl var⟩ {⟨old tokens⟩} {⟨new tokens⟩}-function and then pass the result to \tex_scantokens:D for re-tokenization and re-digestion—the code for this is considerably shorter than the code in my other approach, and you don't need \makeatletter..\makeatother:

Shortcomings:

Due to replacement-directives being in terms of a comma-separated ⟨tokens to replace⟩=⟨replacement⟩-list

  • \ReplacementScope cannot be used for replacing (with) phrases that contain at least one comma.
  • \ReplacementScope cannot be used for replacing (with) phrases that have leading and/or trailing spaces.

As \ReplacementScope processes arguments that are to be read and tokenized under verbatim-catcode-régime, the same restrictions apply as with the command \verb.

\documentclass[a4paper]{article}

%%//////////////////////// Code of \ReplacementScope: /////////////////////////
%%=============================================================================
\RequirePackage{xparse}%
\NewDocumentCommand\ReplacementScope{}{%
  \begingroup
  % Catcode of horizontal tab is not switched to 12(other) by xparse's
  % routine for reading v/+v-type-arguments, so let's do that now:
  \catcode`\^^I=12\relax
  \InnerReplacementScope
}%
\ExplSyntaxOn
\msg_new:nnnn{ReplacementScope}%
             {NoValueError}%
             {Line\ #2\ specifies\ to\ replace\ the\ phrase:\\#1\\But\ a\ replacement\ text\ is\ not\ specified.} 
             {If\ you\ want\ to\ replace\ things,\ then\ you\ need\ to\ specify\ both\ the\ phrase\ to\ replace\ and\ the\ replacement\ text.}
\cs_new:Nn \ReplacementScope_NovalueError:n {
  \group_begin:
  \cs_set:Nn \msg_error_text:n {##1~Error:}
  \msg_error:nnxx {ReplacementScope}{NoValueError}{#1}{\msg_line_number:}
  \group_end:
}
\cs_new:Nn \ReplacementScope_Replace:nn {
  \tl_replace_all:Nnn \l_tmpa_tl {#1}{#2}
}
\group_begin:
\char_set_catcode_other:N \^^M
\use:n{
  \group_end:
  \NewDocumentCommand\InnerReplacementScope{+v+v}{
    \group_end:\group_begin:
    \tl_set:Nn \l_tmpa_tl {#2}
    \keyval_parse:NNn \ReplacementScope_NovalueError:n \ReplacementScope_Replace:nn { #1 }
    \exp_args:Nnc \use:n { \exp_args:Nno \tl_put_right:Nn { \l_tmpa_tl } } {@percentchar}
    \exp_args:Nno \tl_put_left:Nn {\l_tmpa_tl} {\token_to_str:N \endgroup ^^M}
    % \tl_show:N \l_tmpa_tl
    \tex_newlinechar:D=\tex_endlinechar:D
    \exp_args:NV \tex_scantokens:D {\l_tmpa_tl}
  }
}
\ExplSyntaxOff
%%=============================================================================
%%//////////////////// End of code of \ReplacementScope: //////////////////////

\ReplacementScope{VAR_A=#1, VAR_B=#2, VAR_C=#3}{%
  %
  \newcommand{\myfirstcommand}[3]{%
    Argument 1 is VAR_A.\\
    Argument 2 is VAR_B.\\
    Argument 3 is VAR_C.
  }%
  %
}


\ReplacementScope|}VAR_A}=#1, }VAR_B}=#2, }VAR_C}=#3||%
  %
  \newcommand{\mysecondcommand}[3]{%
    Argument 1 is }VAR_A}.\\
    Argument 2 is }VAR_B}.\\
    Argument 3 is }VAR_C}.
  }%
  %
|

\ReplacementScope{}{%
  %
  \newcommand{\mythirdcommand}[3]{%
    Three arguments are gobbled.
    This sentence contains the string VAR_A.\\
    This sentence contains the string VAR_B.\\
    This sentence contains the string VAR_C.\\
  }%
  %
}

\ReplacementScope{INNERVAR_A=##1, INNERVAR_B=##2, INNERVAR_C=##3, VAR_A=#1, VAR_B=#2, VAR_C=#3}{%
  %
  \newcommand{\myfourthcommand}[3]{%
    \newcommand{\myinnerfourthcommand}[3]{%
      Argument 1 is INNERVAR_A.\\
      Argument 2 is INNERVAR_B.\\
      Argument 3 is INNERVAR_C.
    }%
    \myinnerfourthcommand{VAR_A}{VAR_B}{VAR_C}%
  }%
  %
}

\usepackage{tikz}
\usepackage{pgfplots}

\ReplacementScope{MYVALUE=#1}{%
  %
  \newcommand{\mydraw}[1]{%
    \addplot [mark=*] coordinates{(MYVALUE,0.3)};
    \draw node at (axis cs:MYVALUE,0.4) [below] {MYVALUE};
  }%
  %
}%


\begin{document}

\begingroup

\advance\oddsidemargin -.75in\relax
\advance\textwidth 1.5in\relax
\advance\linewidth 1.5in\relax
\advance\hsize 1.5in\relax
\null\kern-1.7in \enlargethispage{.3in}%
\sloppy
\ttfamily\footnotesize\frenchspacing
\parindent=0ex

\begin{verbatim}
\ReplacementScope{VAR_A=#1, VAR_B=#2, VAR_C=#3}{%
  %
  \newcommand{\myfirstcommand}[3]{%
    Argument 1 is VAR_A.\\
    Argument 2 is VAR_B.\\
    Argument 3 is VAR_C.
  }%
  %
}
\end{verbatim}

\string\myfirstcommand=\meaning\myfirstcommand

\vfill\hrule\vfill

\begin{verbatim}
\ReplacementScope|}VAR_A}=#1, }VAR_B}=#2, }VAR_C}=#3||%
  %
  \newcommand{\mysecondcommand}[3]{%
    Argument 1 is }VAR_A}.\\
    Argument 2 is }VAR_B}.\\
    Argument 3 is }VAR_C}.
  }%
  %
|
\end{verbatim}

\string\mysecondcommand=\meaning\mysecondcommand

\vfill\hrule\vfill

\begin{verbatim}
\ReplacementScope{}{%
  %
  \newcommand{\mythirdcommand}[3]{%
    Three arguments are gobbled.
    This sentence contains the string VAR_A.\\
    This sentence contains the string VAR_B.\\
    This sentence contains the string VAR_C.\\
  }%
  %
}
\end{verbatim}

\string\mythirdcommand=\meaning\mythirdcommand

\vfill\hrule\vfill

\begin{verbatim}
\ReplacementScope{INNERVAR_A=##1, INNERVAR_B=##2, INNERVAR_C=##3, VAR_A=#1, VAR_B=#2, VAR_C=#3}{%
  %
  \newcommand{\myfourthcommand}[3]{%
    \newcommand{\myinnerfourthcommand}[3]{%
      Argument 1 is INNERVAR_A.\\
      Argument 2 is INNERVAR_B.\\
      Argument 3 is INNERVAR_C.
    }%
    \myinnerfourthcommand{VAR_A}{VAR_B}{VAR_C}%
  }%
  %
}
\end{verbatim}

\string\myfourthcommand=\meaning\myfourthcommand

\vfill\hrule\vfill

\begin{verbatim}
\ReplacementScope{MYVALUE=#1}{%
  %
  \newcommand{\mydraw}[1]{%
    \addplot [mark=*] coordinates{(MYVALUE,0.3)};
    \draw node at (axis cs:MYVALUE,0.4) [below] {MYVALUE};
  }%
  %
}%
\end{verbatim}

\string\mydraw=\meaning\mydraw

\vfill\hrule\vfill

\newpage

\endgroup

\begin{tikzpicture}
  \begin{axis}

    \addplot coordinates{(0,0) (1,1)};

    \mydraw{0.4}
    \mydraw{0.6}

    \end{axis}
\end{tikzpicture}

\end{document}

enter image description here enter image description here

Not the answer you're looking for? Browse other questions tagged or ask your own question.