3

I want LaTeX to do:

"H(k,c,a,b)=L(a,b,c,d)*T(c,k)",tensor_H,tensor_L2,tensor_t1

from:

\contractFMS{GAB}{H,kacb}{L2,abij}{t1,ck}

I've started with one function, but variables are overwriting.

My MWE:

\documentclass[10pt,reqno,a4paper,twoside,french]{report}
\usepackage{ifthen}
\usepackage{xparse}
\usepackage{pdftexcmds}

%% Name of tenseur currently used
\def\NTenstensor{\texttt{tensor\_}}

\ExplSyntaxOn % To split
\NewDocumentCommand{\SND}{ >{\SplitArgument{2}{,}} m %
}{\pSND#1}%
\NewDocumentCommand{\pSND}{mmm}{%
        \ifthenelse{\equal{#1}{t1}}%
        {   \def\NOM{\NTenstensor#1}%
            \def\Tenseur{\Tsfo #2}%
            \def\Dimension{NaN} }%
        {}% 
        \ifthenelse{\equal{#1}{L2}}%
        {   \def\NOM{\NTenstensor#1}%
            \def\Tenseur{\Ldfo #2}%
            \def\Dimension{NaN} }%
        {}% 
        \ifthenelse{\equal{#1}{H}}%
        {   \def\NOM{\NTenstensor#1}%
            \def\Tenseur{\Hdfo #2}%
            \def\Dimension{NaN} }%
        {}% 
}%
\ExplSyntaxOff

%% To write
\newcommand{\contractFMS}[4]{%
    \ifthenelse{\equal{#1}{GAB}}{
        \SND{#2}%
        \def\NUn{\NOM} 
        \def\TUn{\Tenseur}  
\noindent First Call : \texttt{\TUn,\NUn,}\\
        \SND{#3}%
        \def\NDe{\NOM} 
        \def\TDe{\Tenseur}
\noindent Second Call : \texttt{\TUn,\NUn,}\\
        \SND{#4}%
        \def\NTr{\NOM} 
        \def\TTr{\Tenseur}
\noindent Third Call : \texttt{\TUn,\NUn,}\\        
\noindent           Result : \texttt{"\TUn=\TDe*\TTr",\NUn,\NDe,\NTr}
    }{}%
}%

\newcommand{\Tsfo}[2]{T(#1,#2)}
\newcommand{\Ldfo}[4]{L(#1,#2,#3,#4)}
\newcommand{\Hdfo}[4]{H(#1,#2,#3,#4)}

\begin{document}

\contractFMS{GAB}{H,kacb}{L2,abij}{t1,ck}

\end{document}

I tested the changes with \expandafter but without success.

Ulrich Diez
  • 28,770
Loic
  • 33
  • Welcome to TeX-SX! As a new member, it is recommended to visit the Welcome and the Tour pages to be informed about our format and also to know about Minimal Example. – R. N Feb 15 '20 at 16:01
  • 1
    You seem to hardwire GAB, L2, t1 and H in the code, which seems a waste. Can you please be more specific to the rules you want to apply? – egreg Feb 15 '20 at 21:46

1 Answers1

4

Your comment

Just a little extra help, I would like not to have to use commas as separators but just a space: \contractFMS{GAB}{H kacb}{L2 abij}{t1 ck}, but \SplitArgument {2} {} does not work...

caused me to re-write my answer.

Some preliminary remarks:

In normal LaTeX syntax space characters (), code-point number 32 in the TeX-engine's internal character encoding scheme (which with classic TeX-engines is ASCII and with LuaTeX- or XeTeX-based TeX-engines is UTF8), get tokenized as normal space tokens when the reading-apparatus is in state M (middle of line).
Under normal/under non-expl3 circumstances all space characters at the end of a line of input get removed. Then a return-character (code-point number 13 in the TeX-engine's internal character encoding scheme; \endlinechar-parameter's value usually is 13) is appended at the end of the line of input. The return-character under normal circumstances has category code 5(end of line).
If the reading-apparatus is in state S(skipping blanks), then characters of category code 5 don't yield any token at all.
If the reading-apparatus is in state N(new line), then characters of category code 5 yield the token \par regardless the meaning of \par.
If the reading-apparatus is in state M(middle of line), then characters of category code 5 yield explicit space tokens (character tokens of category code 10(space) and character code 32).
This implies: If the reading-apparatus is in state M(middle of line) when reaching the end of a line of .tex-input, then an explicit space token will be inserted into the token-stream at that place which corresponds to the end of the line in the .tex-source-code. You can avoid this by ending lines of input that don't end with a control-word-token (whereafter the reading-apparatus would be in state S(skipping blanks)) with a comment-character (%). Space tokens in turn yield horizontal glue in (restricted) horizontal mode. Tildes (~) denote non-breaking spaces.

In expl3 syntax things are different: All whitespace (space characters and the like) is ignored. \endlinechar denotes the space-character and thus whitespace that is to be ignored, not a return-character. (So the risk of getting unwanted horizontal glue from spaces that slip into macro-definitons in places/at line-endings where the reading-apparatus will be in state m (middle of line) is eliminated.) Normal space tokens are denoted by means of tildes.

If you wish to use a space token as delimiter instead of a comma, then the argument-delimiter in the macro code should be a space token.
In normal LaTeX syntax the space token in the .tex-input is denoted by a space character.
In expl3 Syntax the space token is denoted by a tilde.

If you wish to have the possibility of either writing a space or writing a comma, then before splitting just add a check for the presence of a comma and if a comma is present, split the argument at the comma, and if no comma is present assume that the argument can be split at the space.

The check for the presence of a comma can be implemented as follows: Append a comma to the argument and then have LaTeX gobble up everything to the first comma. If no comma is present, the result is emptiness/no token at all. If a comma is present, the result is not emptiness/is formed by the tokens between the first comma and the token following the appended comma. I.e., the last token of the result is the appended comma.

The check for the presence of a comma implies the need of checking whether a macro argument is empty. In the examples below \UD@CheckWhetherNull is used for this. \UD@CheckWhetherNull is explained in my answer to the question "Expandable test for an empty token list—methods, performance, and robustness". (In that answer the macro is named \CheckWhetherEmpty.)

As we deal with delimited arguments, surrounding spaces need to be taken into account/surrounding spaces need to be removed.

I suggest using xparse's >{\TrimSpaces}-argument-processor with expl3-syntax.

I also suggest putting only those things into \ifthenelse-branches that are not always the same and nesting the calls to \ifthenelse so that subsequent \ifthenelse get carried out only if needed.

With a few modifications your code becomes:

\documentclass[a4paper]{report}
\usepackage{ifthen}
\usepackage{xparse}

\makeatletter
%%.............................................................................
%% \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]{%
  \romannumeral0\expandafter\@secondoftwo\string{\expandafter
  \@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
  \@secondoftwo\string}\@firstoftwo\expandafter{} \@secondoftwo}%
  {\@firstoftwo\expandafter{} \@firstoftwo}%
}%    
\@ifdefinable\@gobbletocomma{\long\def\@gobbletocomma#1,{}}
\newcommand\CheckWhetherNoComma[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\@gobbletocomma#1,}%
}%

\ExplSyntaxOn % To split
\NewDocumentCommand{\SplitInTwoArgsAtSpace}{ m >{\SplitArgument{1}{~}}m }{#1#2}
\NewDocumentCommand{\SplitInTwoArgsAtComma}{ m >{\SplitArgument{1}{,}}m }{#1#2}
\NewDocumentCommand{\SND}{ m }{
  \CheckWhetherNoComma{#1}{\SplitInTwoArgsAtSpace}{\SplitInTwoArgsAtComma}{\pSND}{#1}
}
\NewDocumentCommand{\pSND}{>{\TrimSpaces}m >{\TrimSpaces}m }{
  \ifthenelse{\equal{#1}{t1}}{\def\Tenseur{\Tsfo #2}\@firstofone}{
    \ifthenelse{\equal{#1}{L2}}{\def\Tenseur{\Ldfo #2}\@firstofone}{
      \ifthenelse{\equal{#1}{H}}{\def\Tenseur{\Hdfo #2}\@firstofone}{\@gobble}
    }
  }
  {
    % These are always the same unless none of the three cases is given:
    \def\NOM{\NTenstensor#1}
    %\def\Dimension{NaN}
  }
}
\NewDocumentCommand{\contractFMS}{>{\TrimSpaces}m>{\TrimSpaces}m>{\TrimSpaces}m>{\TrimSpaces}m}{
  \ifthenelse{\equal{#1}{GAB}}{
    \SND{#2}
    \expandafter\def\expandafter\NUn\expandafter{\NOM}
    \expandafter\def\expandafter\TUn\expandafter{\Tenseur}
    \SND{#3}
    \expandafter\def\expandafter\NDe\expandafter{\NOM}
    \expandafter\def\expandafter\TDe\expandafter{\Tenseur}
    \SND{#4}
    \expandafter\def\expandafter\NTr\expandafter{\NOM}
    \expandafter\def\expandafter\TTr\expandafter{\Tenseur}
    \noindent First Call~:~\texttt{\TUn,\NUn,}\\
    \noindent Second Call~:~\texttt{\TDe,\NDe,}\\
    \noindent Third Call~:~\texttt{\TTr,\NTr,}\\
    \noindent Result~:~\texttt{"\TUn=\TDe*\TTr",\NUn,\NDe,\NTr}
  }{}
}
\ExplSyntaxOff
\makeatother

\newcommand{\Tsfo}[2]{T(#1,#2)}
\newcommand{\Ldfo}[4]{L(#1,#2,#3,#4)}
\newcommand{\Hdfo}[4]{H(#1,#2,#3,#4)}
%% Name of tensor currently used
\newcommand*\NTenstensor{tensor\_}

\begin{document}

\contractFMS{ GAB }{H kacb}{L2 , abij}{t1,ck}

\end{document}

enter image description here

Be aware that this result differs from what you requested in your question:

"H(k,c,a,b)=L(a,b,c,d)*T(c,k)",tensor_H,tensor_L2,tensor_t1

In case the request in your question did not contain typos, please state exact rules for replacement/exchanging of things.

Also be aware that with the approach of \SND/\pSND defining scratch-macros \NOM, \Tenseur and \Dimension (the latter is never used in your example) and expanding them after calling \SND for splitting one of the arguments, you are bound to deliver the t1-, L2-, and H-component in a specific order behind the GAB-component.


If you don't want to be bound to that specific order, the only requirement being that the GAB-component is always the first argument, I suggest not defining scratch macros but passing the tokens which you wish to have defined in the end (\NUn/\TUn, \NDe/\TDe, \NTr/\Ttr) and the macros for processing the splitted components (\Tsfo, \Ldfo, \fHdo, ), that need to go into the macro-definitions, as arguments into the forking-mechanism itself, which in your example is made up via \ifthenelse from the ifthen package.

By the way: As expl3-syntax is used, \tl_if_eq:nnTF is available, thus you probably don't need ifthen.

If in the real-life-scenario/in the use-case you have more components and more cases to distinguish, expl3's \tl_map_tokens:nn for iterating on lists of undelimited arguments might be your friend.

Besides this there is not much \expandafter in the code any more:

\documentclass[a4paper]{report}
\usepackage{xparse}

\makeatletter
%%.............................................................................
%% \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]{%
  \romannumeral0\expandafter\@secondoftwo\string{\expandafter
  \@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
  \@secondoftwo\string}\@firstoftwo\expandafter{} \@secondoftwo}%
  {\@firstoftwo\expandafter{} \@firstoftwo}%
}%    
\@ifdefinable\@gobbletocomma{\long\def\@gobbletocomma#1,{}}
\newcommand\CheckWhetherNoComma[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\@gobbletocomma#1,}%
}%

\ExplSyntaxOn % To split

% \tl_map_tokens:nn  and  \@@_map_tokens:nn  were added to expl3 in 2019-09-05.
% In case your expl3-release is not that recent, define these control sequences here.
% The following is just a copy of the code from l3tl.dtx, released 2020-02-14.
%
% \tl_map_tokens:nn {<list of undelimited arguments>}{<tokens to prepend>}
% iterates on the <list of undelimited arguments> and in each iteration
% prepends <tokens to prepend> to the current element of the list.
% I.e., <tokens to prepend>{<current element of the list>} gets executued,
% then either the next iteration is carried out or iteration is terminated.
\if_cs_exist:N \tl_map_tokens:nn \else
  \cs_new:Npn \tl_map_tokens:nn #1#2
    {
      \@@_map_tokens:nn {#2} #1
      \q_recursion_tail
      \prg_break_point:Nn \tl_map_break: { }
    }
\fi
\if_cs_exist:N \@@_map_tokens:nn \else
  \cs_new:Npn \@@_map_tokens:nn #1#2
    {
      \quark_if_recursion_tail_break:nN {#2} \tl_map_break:
      \use:n {#1} {#2}
      \@@_map_tokens:nn {#1}
    }
\fi

\NewDocumentCommand{\SplitInTwoArgsAtSpace}{ m >{\SplitArgument{1}{~}}m }{#1#2}
\NewDocumentCommand{\SplitInTwoArgsAtComma}{ m >{\SplitArgument{1}{,}}m }{#1#2}
\NewDocumentCommand{\SND}{ m }{
  \CheckWhetherNoComma{#1}{\SplitInTwoArgsAtSpace}{\SplitInTwoArgsAtComma}{\pSND}{#1}
}
\NewDocumentCommand{\pSND}{>{\TrimSpaces}m >{\TrimSpaces}m }{
  % #1 = first component without surrounding spaces
  % #2 = second component without surrounding spaces
  \tl_map_tokens:nn{
     % A List of 5-tuples, each tuple of pattern:
     %{  
     %   {<first component equals>}
     %   {<macro to define from first component in case first component equals>}
     %   {<prefix/preprocessor in macro for first component>}
     %   {<macro to define from second component in case first component equals>}
     %   {<prefix/preprocessor in macro for second component>}
     %} 
     {{t1}{\NTr}{\NTenstensor}{\TTr}{\Tsfo}}
     {{L2}{\NDe}{\NTenstensor}{\TDe}{\Ldfo}}
     {{H}{\NUn}{\NTenstensor}{\TUn}{\Hdfo}}
  }{\pSNDDefine{#1}{#2}}
}
\NewDocumentCommand{\pSNDDefine}{mmm}{\pSNDDefinUnwrapped{#1}{#2}#3}
\NewDocumentCommand{\pSNDDefinUnwrapped}{mmmmmmm}{%
   %#1 - first component
   %#2 - second component
   %#3 - first component equals
   %#4 - macro to define from first component in case first component equals
   %#5 - prefix/preprocessor in macro for first component
   %#6 - macro to define from second component in case first component equals
   %#7 - prefix/preprocessor in macro for second component
   \tl_if_eq:nnTF{#1}{#3}{\def#4{#5#1}\def#6{#7#2}}{}
   %\ifthenelse{\equal{#1}{#3}}{\def#4{#5#1}\def#6{#7#2}}{}
}%
\NewDocumentCommand{\contractFMS}{>{\TrimSpaces}m>{\TrimSpaces}m>{\TrimSpaces}m>{\TrimSpaces}m}{
  \tl_if_eq:nnTF{#1}{GAB}{
    \tl_map_tokens:nn{{#2}{#3}{#4}}{\SND}
    \noindent First Call~:~\texttt{\TUn,\NUn,}\\
    \noindent Second Call~:~\texttt{\TDe,\NDe,}\\
    \noindent Third Call~:~\texttt{\TTr,\NTr,}\\
    \noindent Result~:~\texttt{"\TUn=\TDe*\TTr",\NUn,\NDe,\NTr}
   }{}
}
\ExplSyntaxOff
\makeatother

\newcommand{\Tsfo}[2]{T(#1,#2)}
\newcommand{\Ldfo}[4]{L(#1,#2,#3,#4)}
\newcommand{\Hdfo}[4]{H(#1,#2,#3,#4)}

%% Name of tensor currently used
\newcommand*\NTenstensor{tensor\_}

\begin{document}

\newsavebox\prettyprintbox

\begin{lrbox}{\prettyprintbox}%
\verb|\contractFMS{ GAB }{H kacb}{L2 , abij}{t1,ck}|%
\end{lrbox}%
\begingroup\centering\framebox{\usebox{\prettyprintbox} yields:}\smallskip\par\endgroup
\contractFMS{ GAB }{H kacb}{L2 , abij}{t1,ck}

\noindent\hrulefill\null\vspace{\ht\strutbox}

\begin{lrbox}{\prettyprintbox}%
\verb|\contractFMS{ GAB }{H kacb}{t1 ck}{L2 , abij}|%
\end{lrbox}%
\begingroup\centering\framebox{\usebox{\prettyprintbox} yields:}\smallskip\par\endgroup
\contractFMS{ GAB }{H kacb}{t1 ck}{L2 , abij}

\noindent\hrulefill\null\vspace{\ht\strutbox}

\begin{lrbox}{\prettyprintbox}%
\verb|\contractFMS{ GAB }{t1,ck}{H kacb }{ L2  abij }|%
\end{lrbox}%
\begingroup\centering\framebox{\usebox{\prettyprintbox} yields:}\smallskip\par\endgroup
\contractFMS{ GAB }{t1,ck}{H kacb }{ L2  abij }

\noindent\hrulefill\null\vspace{\ht\strutbox}

\begin{lrbox}{\prettyprintbox}%
\verb|\contractFMS{GAB}{t1 ,ck}{L2 , abij}{H kacb}|%
\end{lrbox}%
\begingroup\centering\framebox{\usebox{\prettyprintbox} yields:}\smallskip\par\endgroup
\contractFMS{GAB}{t1 ,ck}{L2 , abij}{H kacb}

\noindent\hrulefill\null\vspace{\ht\strutbox}

\begin{lrbox}{\prettyprintbox}%
\verb|\contractFMS{ GAB}{L2 , abij}{ t1, ck}{H kacb}|%
\end{lrbox}%
\begingroup\centering\framebox{\usebox{\prettyprintbox} yields:}\smallskip\par\endgroup
\contractFMS{ GAB}{L2 , abij}{ t1, ck}{H kacb}

\noindent\hrulefill\null\vspace{\ht\strutbox}

\begin{lrbox}{\prettyprintbox}%
\verb|\contractFMS{GAB }{ L2  abij }{ H kacb }{ t1 ck  }|%
\end{lrbox}%
\begingroup\centering\framebox{\usebox{\prettyprintbox} yields:}\smallskip\par\endgroup
\contractFMS{ GAB }{ L2  abij }{ H kacb }{ t1 ck  }

\end{document}

enter image description here

Ulrich Diez
  • 28,770
  • Just a little extra help, I would like not to have to use commas as separators but just a space: \contractFMS{GAB}{H kacb}{L2 abij}{t1 ck}, but \SplitArgument {2} {} does not work ... – Loic Feb 16 '20 at 06:14
  • @Loic I rewrote my answer to include the possibility of separating things by a space instead of a comma. Also handling of spurious leading and trailing spaces of arguments is taken into account now. – Ulrich Diez Feb 16 '20 at 13:44
  • @Loic I just rewrote my answer again to loosen restrictions regarding the order in which components of \contractFMS must be delivered. – Ulrich Diez Feb 17 '20 at 18:03