4

I would like to write a command \TransTM that expands, for example, \TransTM{a, x, R | b, y, L} to \shortstack{a;~x,~R \\ b;~y,~L}.

The MWE

\documentclass{article}

\usepackage{lmodern}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand \TransTM { m  }
  { \edge_label:cn {edge_item_tm:nnn} {#1} }

\cs_new_protected:Npn \edge_label:cn #1 #2
{
  \seq_set_split:Nnn \l_tmpa_seq { | } { #2 } 
  \seq_set_map:NNn \l_tmpb_seq \l_tmpa_seq { \edge_item:cn {#1} {##1} }
  \shortstack{ \seq_use:Nn \l_tmpb_seq { \\ } }
}

\cs_new_protected:Npn \edge_item:cn #1 #2
{
  \seq_set_split:Nnn \l_tmpa_seq { , } { #2 }
  \seq_set_map:NNn \l_tmpb_seq \l_tmpa_seq { {##1} }
  \use:c { #1 } x x x % \seq_use:Nn \l_tmpb_seq { }
}

\cs_new_protected:Npn \edge_item_tm:nnn #1 #2 #3
{
  #1;~#2,~#3
}

\ExplSyntaxOff

\begin{document}

\TransTM{a, x, L | b, y, R}

\end{document}

works only partly, since I have managed to split the argument at | on the first level, but not at , on the second level. \seq_use:Nn in \edge_item:cn is commented out and replaced by the hard-coded x x x because it produces an error.

What is wrong in attempting to split the argument on the second level?

Note: There will be different variants in future. This is why I pass the name of the function edge_item_tm:nnn as an argument. Another document-level command \TransFA{a | b | c}, for example, is supposed to expand to \shortstack{a \\ b \\ c}. The difference will be only in the structure of the parts between the | tokens.

Addendum

It seems I oversimplified the above MWE. The following code

\documentclass{article}

\usepackage{lmodern}
\usepackage{xparse}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage{relsize}

\ExplSyntaxOn

\NewDocumentCommand \Char { O{\width} m } 
{
  \makebox[#1] 
  {
    \str_case_x:nnF { \tl_to_str:n {#2} }
    {
      {              } { $\varepsilon$           }
      { ##           } { \texttt{\#}             }
      { \c_tilde_str } { \textscale{.87}{$\Box$} }
    }
    { \texttt{#2} }
  }
}

\NewDocumentCommand \TransTM { m }
  { \__edge_label_tm:n {#1} }

\cs_new_protected:Npn \__edge_label_tm:n #1 
{
  \seq_set_split:Nnn \l_tmpa_seq { | } { #1 } 
  \seq_set_map:NNn \l_tmpb_seq \l_tmpa_seq { \SplitItemTM \exp_not:n { {##1} } }
  \shortstack{ \seq_use:Nn \l_tmpb_seq { \\ } }
}

\NewDocumentCommand \SplitItemTM { >{\SplitArgument{2}{,}} m }
  { \__edge_item_tm:nnn #1 }

\cs_new_protected:Npn \__edge_item_tm:nnn #1 #2 #3 
{
  \Char[.63em]{#1};\;\Char[.63em]{#2},\,\Char{#3}
}

\ExplSyntaxOff

\begin{document}

\TransTM{a, x, L} \qquad
\TransTM{a, x, L | b, y, L} \qquad
\TransTM{a, x, L | b, y, L | ~, ~, R}

\end{document}

yields the desired result.

desired result

However, it misuses \NewDocumentCommand since \SplitItemTM belongs to the implementation and not to the user interface. What would be the right way to replace \SplitItemTM by an internal function?

Matthias
  • 1,956

2 Answers2

7

First off: you should not define with \cs_new_protected:Npn a function with signature :cn.

I take that your input is of the form \TransTM{a, x, R | b, y, L | c, m, n, p} and you want to isolate the first item in each |-separated part for using a semicolon after it.

First split the argument at | and perform a mapping adding an auxiliary function around each item. This auxiliary macro will act when the row in \shortstack will be printed: isolate the first item, print it followed by a semicolon and space, then deliver the rest of the sequence items separated by comma and space.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\TransTM}{m}
 {
  \matthias_edge_label:n { #1 }
 }

\seq_new:N \l__matthias_edge_labels_in_seq
\seq_new:N \l__matthias_edge_labels_out_seq
\seq_new:N \l__matthias_edge_label_seq
\tl_new:N \l__matthias_edge_label_head_tl

\cs_new_protected:Nn \matthias_edge_label:n
 {
  % first let's split at |
  \seq_set_split:Nnn \l__matthias_edge_labels_in_seq { | } { #1 }
  % populate the sequence for output
  \seq_set_map:NNn
   \l__matthias_edge_labels_out_seq
   \l__matthias_edge_labels_in_seq
   { \__matthias_edge_label:n { \exp_not:n { ##1 } } }
  % deliver it
  \shortstack{ \seq_use:Nn \l__matthias_edge_labels_out_seq { \\ } }
 }

\cs_new_protected:Nn \__matthias_edge_label:n
 {
  % split the argument at commas
  \seq_set_split:Nnn \l__matthias_edge_label_seq { , } { #1 }
  % remove the first item
  \seq_pop_left:NN \l__matthias_edge_label_seq \l__matthias_edge_label_head_tl
  \tl_use:N \l__matthias_edge_label_head_tl
  ;~
  \seq_use:Nn \l__matthias_edge_label_seq { ,~ }
 }
\ExplSyntaxOff

\begin{document}

\TransTM{a, x, L | b, y, R}
\qquad 
\TransTM{a, x, R | b, y, L | c, m, n, p}

\end{document}

enter image description here

Now, let's see what went wrong with your approach. When you do

\TransTM{a, x, L | b, y, R}

the sequence \l_tmpb_seq will contain the items

{\edge_item:cn {edge_item_tm:nnn} {a, x, L}}
{\edge_item:cn {edge_item_tm:nnn} {b, y, R}}

(outer braces for clarity, but not really there). When the sequence is delivered, you get

\shortstack{%
  \edge_item:cn {edge_item_tm:nnn} {a, x, L}\\%
  \edge_item:cn {edge_item_tm:nnn} {b, y, R}%
}

(lines split for ease of reading). Now your idea is to split the items in the second argument at commas, and apply a three argument function. But if you do

\use:c{edge_item_tm:nnn} \seq_use:Nn \l_tmpb_seq { }

the first argument to \edge_item_tm:nnn will be \seq_use:Nn, because this hasn't yet been expanded.

This can be done, however, but I don't recommend this approach. In any case, the c arguments are out of place.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand \TransTM { m  }
  { \edge_label:Nn \edge_item_tm:nnn {#1} }

\cs_new_protected:Npn \edge_label:Nn #1 #2
{
  \seq_set_split:Nnn \l_tmpa_seq { | } { #2 } 
  \seq_set_map:NNn \l_tmpb_seq \l_tmpa_seq { \edge_item:Nn #1 {##1} }
  \shortstack{ \seq_use:Nn \l_tmpb_seq { \\ } }
}

\cs_new_protected:Npn \edge_item:Nn #1 #2
{
  \seq_set_split:Nnn \l_tmpa_seq { , } { #2 }
  \seq_set_map:NNn \l_tmpb_seq \l_tmpa_seq { {##1} }
  \exp_last_unbraced:Nf #1 \seq_use:Nn \l_tmpb_seq { }
}

\cs_new_protected:Npn \edge_item_tm:nnn #1 #2 #3
{
  #1;~#2,~#3
}

\ExplSyntaxOff

\begin{document}

\TransTM{a, x, L | b, y, R}

\end{document}

ADDENDUM

You're right in thinking that \SplitItemTM doesn't belong there.

\documentclass{article}
\usepackage{xparse}
\usepackage{graphicx,amssymb}

\ExplSyntaxOn

\NewDocumentCommand \Char { O{\width} m } 
 {
  \makebox[#1]{ \edge_char:n { #2 } }
 }

\NewDocumentCommand \TransTM { m }
 {
  \edge_label:n {#1}
 }

\seq_new:N \l__edge_labels_in_seq
\seq_new:N \l__edge_labels_out_seq

\cs_new_protected:Nn \edge_char:n
 {
  \str_case_x:nnF { \tl_to_str:n {#1} }
   {
    {              } { $\varepsilon$           }
    { ##           } { \texttt{\#}             }
    { \c_tilde_str } { \scalebox{.87}{$\Box$}  }
   }
  { \texttt{#1} }
 }
\cs_generate_variant:Nn \edge_char:n { f }

\cs_new_protected:Nn \edge_label:n
 {
  \group_begin:
  % this because otherwise boxes would not print
  \char_set_active_eq:nN { `\~ } \c_tilde_str

  \seq_set_split:Nnn \l__edge_labels_in_seq { | } { #1 }
  \seq_set_map:NNn
   \l__edge_labels_out_seq
   \l__edge_labels_in_seq
   { \edge_item:n { \exp_not:n { ##1 } } }
  \shortstack
   {
    \seq_use:Nn \l__edge_labels_out_seq { \\ }
   }
  \group_end:
 }

\cs_new_protected:Nn \edge_item:n
 {
  \edge_char:f { \clist_item:nn { #1 } {1} } ; ~
  \edge_char:f { \clist_item:nn { #1 } {2} } , ~
  \edge_char:f { \clist_item:nn { #1 } {3} }
}

\ExplSyntaxOff

\begin{document}

\TransTM{a, x, L} \qquad
\TransTM{a, , L | b, #, L} \qquad
\TransTM{a, x, L | b, y, L | ~, ~, R}

\end{document}

enter image description here

egreg
  • 1,121,712
  • +1 Is there any reason to prefer \seq_set_split:Nnn over \seq_set_from_clist:Nn to split the comma separated list? –  Nov 14 '17 at 21:36
  • 1
    @Andrew Not really, but \seq_set_split:Nnn is more general. – egreg Nov 14 '17 at 21:37
  • @egreg Thank you so much for the detailed explanation. However, I was not yet able to achieve my goal. Could you please have a look at my addendum? – Matthias Nov 15 '17 at 00:02
  • 3
    IIRC \seq_set_split:Nnn does not remove spaces around items (and one set of braces) and it does not remove empty entries. In contrast, \seq_set_from_clist:Nn treats its arg as a clist hence does the normalization I just said. – Bruno Le Floch Nov 15 '17 at 00:59
  • @Matthias Had a look. ;-) – egreg Nov 15 '17 at 08:20
  • @egreg That is exactly what I was looking for. The second example needs to be \TransTM{a, {}, L | b, #, L} to print the epsilon at the correct position, which is perfectly adequate for my purposes. – Matthias Nov 15 '17 at 11:22
  • @Matthias Perhaps using ? or - instead of empty is better. – egreg Nov 15 '17 at 11:57
2

A listofitems alternative-to-L3 version. EDITED to remove the need for argument expandability, which is demonstrated by using the unexpandable \ddag in the argument.

\documentclass{article}
\usepackage{listofitems}
\newcommand{\addtotoks}[2]{#1\expandafter{\the#1#2}}
\newcommand{\xxaddtotoks}[2]{\expandafter\expandafter\expandafter\addtotoks%
  \expandafter\expandafter\expandafter#1\expandafter\expandafter\expandafter{#2}}
\newtoks\zztoks
\newcommand\TransTM[1]{%
  \setsepchar{{|}/,}%
  \readlist*\zz{#1}%
  \zztoks{}%
  \foreachitem\x\in\zz[]{%
   \if\xcnt=1\else\addtotoks\zztoks{\\}\fi
   \xxaddtotoks\zztoks{\zz[\xcnt,1];~}%
   \xxaddtotoks\zztoks{\zz[\xcnt,2],~}%
   \xxaddtotoks\zztoks{\zz[\xcnt,3]}%
  }%
  \expandafter\shortstack\expandafter{\the\zztoks}%
}
\begin{document}
\TransTM{a, x, L | b, y, R}\quad%
\TransTM{a, x, L | b, y, R | c, y, \ddag}
\end{document}

or if one does not like token lists, then with \defs (via \g@addto@macro):

\documentclass{article}
\usepackage{listofitems}
\makeatletter
\newcommand\TransTM[1]{%
  \setsepchar{{|}/,}%
  \readlist*\zz{#1}%
  \def\zzz{}%
  \foreachitem\x\in\zz[]{%
   \if\xcnt=1\else\g@addto@macro\zzz{\\}\fi
   \addzzz{\zz[\xcnt,1];~}%
   \addzzz{\zz[\xcnt,2],~}%
   \addzzz{\zz[\xcnt,3]}%
  }%
  \expandafter\shortstack\expandafter{\zzz}%
}
\newcommand\addzzz[1]{\expandafter\expandafter\expandafter\g@addto@macro%
  \expandafter\expandafter\expandafter\zzz\expandafter\expandafter\expandafter{#1}}
\makeatother
\begin{document}
\TransTM{a, x, L | b, y, R}\quad%
\TransTM{a, x, L | b, y, R | c, y, \ddag}
\end{document}

enter image description here


A version that shows that \Char can be used with this approach:

\documentclass{article}
\usepackage{listofitems,xparse,relsize,amssymb}
\newcommand{\addtotoks}[2]{#1\expandafter{\the#1#2}}
\newcommand{\xxaddtotoks}[2]{\expandafter\expandafter\expandafter\addtotoks%
  \expandafter\expandafter\expandafter#1\expandafter\expandafter\expandafter{#2}}
\def\ZZ{\Char[.5em]{~}}%
\newtoks\zztoks
\newcommand\TransTM[1]{%
  \setsepchar{{|}/,}%
  \readlist*\zz{#1}%
  \zztoks{}%
  \foreachitem\x\in\zz[]{%
   \if\xcnt=1\else\addtotoks\zztoks{\\}\fi
   \xxaddtotoks\zztoks{\zz[\xcnt,1];~}%
   \xxaddtotoks\zztoks{\zz[\xcnt,2],~}%
   \xxaddtotoks\zztoks{\zz[\xcnt,3]}%
  }%
  \texttt{\expandafter\shortstack\expandafter{\the\zztoks}}%
}
\ExplSyntaxOn

\NewDocumentCommand \Char { O{\width} m } 
{
  \makebox[#1] 
  {
    \str_case_x:nnF { \tl_to_str:n {#2} }
    {
      {              } { $\varepsilon$           }
      { ##           } { \texttt{\#}             }
      { \c_tilde_str } { \textscale{.87}{$\Box$} }
    }
    { \texttt{#2} }
  }
}

\ExplSyntaxOff

\begin{document}
\TransTM{a, x, L | b, y, R}\quad%
\TransTM{a, x, L | b, y, R | \ZZ, y, \ddag}
\end{document}

enter image description here

  • Thank you for your proposal. Could that be extended to an arbitary number of items on the first level? \TransTM{a, x, L} and \TransTM{a, x, L | b, y, R} and \TransTM{a, x, L | b, y, R | c, y, L} and so on are expected to work. – Matthias Nov 14 '17 at 20:49
  • @Matthias See edit. – Steven B. Segletes Nov 14 '17 at 20:57
  • @StevenBSegletes Amazing. The whole functionality in a couple of lines of code. – Matthias Nov 14 '17 at 21:00
  • @Matthias This does require, though, that the argument pieces are expandable. If that may not always be the case, I could rework without the \edef. – Steven B. Segletes Nov 14 '17 at 21:02
  • @StevenBSegletes In real life, the argument pieces need to be wrapped in \Char{} according to this question. There may be calls such as \TransTM{~, x, L | ~, ~, R}. Could that cause any trouble using your approach? – Matthias Nov 14 '17 at 21:16
  • @Matthias With my edits to remove the \edef, there should be no problems anymore if an argument is unexpandable, since now the actual tokens of the argument list are preserved intact. – Steven B. Segletes Nov 14 '17 at 21:22
  • @StevenBSegletes What is the right place to call \Char according to my addendum within your implementation of \TransTM? – Matthias Nov 15 '17 at 00:04
  • @Matthias I can invoke \TransTM{a, x, L | b, y, R | \Char[.63em]{~}, y, \ddag}, once I supply the xparse,relsize,amssymb packages and some of your macros. – Steven B. Segletes Nov 15 '17 at 00:41