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?

3
  • Isn't \shortstack a stackengine macro?
    – user31729
    Nov 14, 2017 at 20:43
  • 1
    @ChristianHupfer No, \shortstack is the "built-in" version. \Shortstack and \Longstack are defined in stackengine. However, the stacking part works as expected.
    – Matthias
    Nov 14, 2017 at 20:45
  • Yes, I confused it.
    – user31729
    Nov 14, 2017 at 20:47

2 Answers 2

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

7
  • +1 Is there any reason to prefer \seq_set_split:Nnn over \seq_set_from_clist:Nn to split the comma separated list?
    – user30471
    Nov 14, 2017 at 21:36
  • 1
    @Andrew Not really, but \seq_set_split:Nnn is more general.
    – egreg
    Nov 14, 2017 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, 2017 at 0: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. Nov 15, 2017 at 0:59
  • @Matthias Had a look. ;-)
    – egreg
    Nov 15, 2017 at 8:20
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

8
  • 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, 2017 at 20:49
  • @Matthias See edit. Nov 14, 2017 at 20:57
  • @StevenBSegletes Amazing. The whole functionality in a couple of lines of code.
    – Matthias
    Nov 14, 2017 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. Nov 14, 2017 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, 2017 at 21:16

You must log in to answer this question.

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